commit 496e8bdb27c58aa34f994b85ef5caebd54fc6889 Author: Przemek Zatorski Date: Thu Sep 3 10:39:26 2026 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b47c93d --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Sekrety formularzy - klucz API i sekret podpisu tokenów. +# Na produkcji te wartości siedzą w /etc/hydronexis/config.php. +api/config.local.php + +# Dane robocze send.php: zużyte tokeny, liczniki IP, log wysyłek. +api/storage/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..4fcbdfb --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,128 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project status + +The site is **built and live in this repo** as static HTML/CSS: [index.html](index.html), [szkolenia.html](szkolenia.html), [konsulting.html](konsulting.html), [index-en.html](index-en.html), sharing [assets/styles.css](assets/styles.css). + +There is no build system, package manager, test suite, or linter — edit the HTML/CSS directly. The repo lives under an XAMPP `htdocs` directory, so preview by opening the files or via `http://localhost/softspm_page_2026/`. + +To check a change visually, headless Edge works: +`"/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe" --headless --disable-gpu --hide-scrollbars --window-size=1400,2000 --screenshot=out.png --virtual-time-budget=8000 "file:///C:/Programy/xampp/htdocs/softspm_page_2026/index.html"` +Note it lays out narrow windows at a 481px minimum, so a 390px shot is cropped, not overflowing — verify real overflow by comparing `documentElement.scrollWidth` with `clientWidth`. + +## What the site is + +SoftSPM is a Polish software house (**25+ years in IT**) that has served the water & sewage (`wod-kan`) sector since 2014. The site sells **turnkey applications, consulting, training and webinars** - in that order of prominence (owner brief, 2026-09-01). + +**Two timelines, do not conflate them:** 25+ years of IT experience, and wod-kan specialisation since 2014. The hero and the numbers sheet both carry the pair. + +**The pump-control heritage is deliberately out of scope.** On the owner's instruction (2026-08-31) all pump diagnostics, SCADA/IoT, energy-optimisation and telemetry content was removed. Do not reintroduce it — no pumps, wells, SUW energy audits, sensors, or failure prediction. Course topics are now regulatory and organisational: water law and tariff applications, utility management, public procurement, customer service and billing, digitisation. + +- Primary CTAs: enroll in training / book a consultation / describe your project. +- **Turnkey software is a first-class offering** (owner brief, 2026-09-01), not an afterthought: custom web systems and **mobile apps** (Android/iOS), built to the client's requirements, delivered end to end without subcontractors, with source code and docs handed to the client. It also covers **advisory on large integrated-system rollouts** (ERP, GIS, billing) - always on the buyer's side, never the vendor's. Carried by `index.html#aplikacje` (section 06 + sheet 03), the `02.1` offer card, and area `02.5` on the consulting page. The course platform, Wodociagowiec and Gielda Komunalna are cited as our own builds - the proof. +- **Offer card order is fixed**: aplikacje pod klucz, konsulting, szkolenia, webinaria. +- The **ecosystem is a headline theme**, not a footnote. `index.html#ekosystem` carries it with a diagram and three "loop" cards. + - **[wodociagowiec.pl](https://wodociagowiec.pl/)** - trade portal: articles, cases, regulatory analysis, reader Q&A. Reader questions become webinar topics. + - **[gieldakomunalna.pl](https://gieldakomunalna.pl/)** - a **new, free marketplace** for the municipal sector (owner brief, 2026-08-31). Two jobs, and copy must reflect both: (1) users **list, buy, sell and swap free of charge** the stock **sitting idle in their warehouses** - project surplus, equipment replaced in upgrades, spares for discontinued machines; (2) it **brings together manufacturers and contractors** in a genuinely **niche, specialist** trade where there may be only a handful nationwide. It is **not** a tender/RFQ portal - do not describe it as "zapytania ofertowe", "miejsce wymiany ofert" or a procurement channel. +- Content language is **Polish**, plus an English mirror of the homepage. + +## Authoritative source of truth + +[doc/SoftSPM_projekt.doc](doc/SoftSPM_projekt.doc) — "SoftSPM — projekt nowej strony internetowej", spec v1.0, August 2026. Despite the `.doc` extension it is plain HTML (Word-flavored, **CP1250-encoded**); read it with an explicit encoding, not as UTF-8. This document defines the site structure, the design system, and the open questions. **When any other file in `doc/` contradicts it, this spec wins.** + +## Site structure (as built) + +| Page | Sections | +|---|---| +| [index.html](index.html) | Hero · By the numbers (spec sheet) · Offer · Subject tracks · Upcoming dates · Course platform · **Ecosystem** · Testimonial · Newsletter | +| [szkolenia.html](szkolenia.html) | Hero · Training format (spec sheet) · Course catalogue (tracks 01-08 with prices, plus an RFQ workshop) · Schedule · Annual webinar package · Signup form | +| [konsulting.html](konsulting.html) | Hero · 7 service areas (02.7 is `.cell-span` full-width) · 3-step process · Deliverables + projects (spec sheet) · Team · Testimonial · Contact form | +| [index-en.html](index-en.html) | English mirror of the homepage - same eight tracks, same ecosystem loop cards; keep it in step when the Polish homepage changes | + +The **eight** subject tracks on `index.html#tematy`, in fixed order: 01 water law & tariff applications, 02 utility management, 03 public procurement, 04 customer service/contracts/billing, then the technical block - 05 SCADA management & network monitoring, 06 security audits & NIS2 compliance, 07 integration with ERP/CRM/tax systems - and 08 digitisation last, framed as the track that ties the others together. Tracks 07 and 08 use a wide `.course` card with a `.split` inside (graphic on opposite sides, mirrored). `szkolenia.html` mirrors the same eight tracks and numbering (ids `sciezka-01`..`sciezka-08`, plus `sciezka-rfq` for the workshop, which sits last and carries no track number). Consulting areas 02.6 (security/NIS2) and 02.7 (integrations) are the advisory counterparts of tracks 06 and 07. + +## Design system — "Industry" / HydroNexis + +A **light wireframe/blueprint** aesthetic: light technical ground, a single steel-blue accent, square corners with registration-cross ("+") marks, cards drawn as line art with no fill, photos duotoned in the accent. Numbers and parameters are presented as **technical spec sheets** — a table with a title block carrying a drawing code, revision, and sheet number. + +Rule: **one accent color only.** No extra decorative colors; hover/pressed states come from the tonal 100–900 ramp. + +| Role | Value | +|---|---| +| Background | `#f2f2f3` | +| Text | `#1d1f20` | +| Accent (steel) | `#5980a6` | +| Accent for paragraph-size text / pressed | `--color-accent-700` (`#416180`) | + +Typography: **Barlow Condensed** (600) for headings, uppercase in section and table titles; **Barlow** for body. Vertical rhythm is a **24px leading unit** with a 12px half-step — line heights and block margins are multiples of it. Icons: Lucide, stroke 1.5. + +Components per spec: primary button is the only fully-filled object (accent fill, square corners, corner marks); ghost buttons are outlined. Offer cards are a hairline frame plus 4 corner crosses, no background. The dates table uses tabular figures, Barlow Condensed topics, Szkolenie/Webinar tags, and a "Zapisz się →" link — rows collapse into cards on mobile. + +The tokens and components above are implemented in [assets/styles.css](assets/styles.css) — the single stylesheet all four pages share. Key classes: `.plate` + `.title-block` (technical sheet), `.corner` (registration crosses, four spans per framed object), `.cell-frame` (line-art card), `.course` (course card with cover), `.kicker` + `.caption-rule` (numbered section label), `.table.spec` / `.table.sched` (both collapse to cards under 760px), `.checks`, `.steps`, `.eco-item`, `.cta-band`. + +### Imagery — custom SVG line art + +Photographs were dropped along with the pump content. All illustrations are **hand-authored SVGs in [assets/img/](assets/img/)**, drawn in the accent colour on a blueprint grid: `szkolenia`, `webinaria`, `konsulting`, `platforma`, `ekosystem` (the ecosystem loop diagram), plus six 480×270 course covers (`kurs-*.svg`). They are already in the system palette, so they use `.plot`, **not** `.duotone`. + +**The English page has its own copies.** [assets/img/en/](assets/img/en/) holds the fourteen graphics `index-en.html` uses — same filenames, same geometry, English labels and ``. Polish pages read from `assets/img/`, `index-en.html` reads only from `assets/img/en/`. Edit a drawing's geometry and you must edit both copies; `konsulting.svg` and `webinaria.svg` have no English twin because no English page uses them. + +Labels use `textLength` + `lengthAdjust="spacingAndGlyphs"` so a string fits its slot whatever the loaded font — keep that attribute when translating, and set the width from the slot, not the word. Too tight a `textLength` swallows the spaces between words (`FIELD CREWAPP`); check the render, not just the markup. Watch for text overflowing its frame and for labels colliding with the corner marks — both bit during the build. + +### The reference mockup + +[doc/SoftSPM_makieta.html](doc/SoftSPM_makieta.html) is the approved homepage mockup and the best guide to markup, section order, and Polish copy. It is a **self-extracting bundle**: the real page lives in a JSON string inside `<script type="__bundler/template">`, with assets base64'd in `__bundler/manifest` and the design system (`_ds_bundle.js`, `styles.css`) in `__bundler/ext_resources`. To read the actual markup, parse out the template script and JSON-decode it rather than reading the file top-down. + +`styles.css` in that bundle is the token/component source of truth for the system — it defines `--color-*` ramps, `--font-*`, `--space-*`, `--radius-*`, `--shadow-*`, and classes including `.btn`/`.btn-primary`/`.btn-ghost`, `.card`, `.table`, `.tag`, `.input`, `.field`, `.nav`, `.dialog`, `.blueprint`, `.duotone`. The extracted template carries only page-level scaffolding (`--leading`, `--half`, `--edge`, `--measure`, `.wrap`, `.hero`, `.plate`, `.kicker`) and pulls everything visual from those tokens. Preserve that split when building real pages: layout in the page, look in the shared stylesheet. + +The mockup also uses an `<image-slot>` custom element for photos and supports a baseline-grid overlay (append `?baselines` to the URL, or press `B`). + +## The competing dark design (do not build from it by default) + +[doc/stitch_softspm_academy_consulting/](doc/stitch_softspm_academy_consulting/) holds four Google Stitch mockups — homepage, training catalog, consulting, and a student dashboard (`panel_kursanta`) — with `screen.png` renders beside each `code.html`. These use a **completely different direction**: dark "Hydro-Tech" theme on `#101415`, Space Grotesk + Hanken Grotesk, Tailwind CDN with an inline `tailwind.config`, and Material Symbols icons. Its tokens are documented in [DESIGN.md](doc/stitch_softspm_academy_consulting/hydro_tech_intelligence/DESIGN.md). + +This contradicts the approved spec. Treat these as exploratory alternatives and as a **content/IA source** — particularly the training catalog and student dashboard, which have no counterpart in the makieta. If a task seems to call for the dark theme, confirm with the user first. Note their copy mixes Polish and English and their dates say 2024 while the spec is 2026. + +## Content that is deliberately placeholder + +The spec lists these as unconfirmed — do not present them as fact, and flag them when touching: + +- Stats in "Doświadczenie w liczbach" (480+ trained, 24 webinars/year, 60+ advisory projects) — sample values. +- Testimonials and quotes — samples, to be replaced with real references. +- Course platform name and URL (the "Przejdź do platformy" links point at `#`). +- Course prices and the annual webinar package price. +- **Training dates are deliberately absent.** The schedule tables carry a `.tag-soon` "Wkrótce" / "Soon" chip instead of a date, and the CTA is "Powiadom mnie" / "Notify me" — the site collects interest rather than taking bookings. Do not reintroduce concrete dates until real ones are confirmed; if they are, restore the `Data`/`Date` column heading and the "Zapisz się" CTAs together with them. +- Team member names — the consulting page lists teams, not people, pending real bios. +- Nothing else. The forms are live — see **Forms** below. + +Every such block already carries a visible caveat (`.sheet-note` or `.form-note`) in the built pages — keep it when editing values. + +Known real details: SoftSPM · Ławy, 97-400 Bełchatów · info@softspm.com. + +## Logos and images + +[images/](images/) holds the brand assets: `spm_logo_new.png` is the **full lockup used in every nav and footer**, `softspm_large.png` / `softspm_small.png` are the drop mark alone, `softspm_full.png` an older lockup, and `favicon.ico` the tab icon. The logo's steel-blue drop matches the `#5980a6` accent, which is why the palette works with it. + +The folder also holds AI-generated photos from the previous direction (SCADA, control rooms, dashboards, blog imagery). **The built pages no longer reference any of them** — they belong to the dropped pump/monitoring theme. [images/README.md](images/README.md) is inherited from an even earlier "AquaNexis" branding and is stale; verify a file exists before referencing it. + +## Forms + +All four forms — newsletter (`index.html`), contact (`index-en.html`), training signup (`szkolenia.html`), consulting (`konsulting.html`) — post JSON to [api/send.php](api/send.php), which relays to `api.softspm.pl/send_email_api.php` ([doc/manual_send_email.html](doc/manual_send_email.html)). **Never call that API from the browser**: it authenticates with a Bearer key, and a key in client-side JS is a public key. The proxy keeps it server-side and pins the recipient to server config, so the endpoint cannot be used to mail arbitrary addresses. + +Setup, the exact entry for `/etc/hydronexis/config.php`, the anti-bot layers and post-deploy tests live in [api/README.md](api/README.md). Needs PHP 7.4+. + +**Message bodies are plain text, never HTML.** The remote API sanitises the body before dropping it into its own template, so any markup arrives in the mailbox as visible `<p style="...">`. This bit once, in production. The layout also survives a template that does not turn `\n` into `<br>`: one `Label: value` per line, readable even when collapsed into a paragraph. + +Front-end is [assets/forms.js](assets/forms.js) plus the `.verify`, `.consent`, `.hp` and `.form-status` blocks in the stylesheet. Every form carries, in this order: fields, a **required** marketing-consent checkbox (`data-consent`, its text stored with the message as proof of what was agreed), a slider (`data-verify`), the submit button, a `data-form-status` live region, and a `<noscript>` fallback pointing at info@softspm.com. + +Adding a form means adding it in **three** places: the markup, the `spm_forms()` whitelist in `api/send.php` (fields, labels, limits, subject line — the server ignores anything not listed), and nothing else. Field `name` attributes must match the whitelist keys. Keep `firma_www` as the honeypot name. + +## Conventions + +- Polish content needs correct diacritics and `<html lang="pl">` (`lang="en"` on `index-en.html`); always write files as UTF-8. +- **Never use an em dash (`—`) in user-visible copy.** The owner asked for a plain hyphen (`-`) throughout: `Uczymy tego, co sami robimy - a tematy bierzemy z rynku.` This covers page text, SVG `<title>` elements, and CSS comments. The en dash (`–`) survives only in numeric ranges such as `8–16 h`. +- Keep the numbering grammar of the section kickers (`02 · Oferta`, `03 · Ścieżki tematyczne`, …) — sections read as numbered notes around the drawn spec sheets. Renumber consistently when inserting a section. +- Every framed object (`.plate`, `.cell-frame`, `.course`, `.figure`, `.cta-band`, `.eco-item`) needs its four `<span class="corner tl/tr/bl/br">` children — that is the system's card grammar, not decoration. +- Spec sheets carry a title block with a drawing code, revision and sheet number (`Arkusz NN z 06`); keep the sequence unique across pages - index 01-03, szkolenia 04-05, konsulting 06. `index-en.html` mirrors sheets 01-03 as `Sheet NN of 06`. Update the denominator on every sheet when adding one. +- The nav collapses under 900px to logo + `.nav-keep` items (EN switch and the CTA); mark anything that must survive on mobile with `nav-keep`. diff --git a/api/README.md b/api/README.md new file mode 100644 index 0000000..5f7d0c2 --- /dev/null +++ b/api/README.md @@ -0,0 +1,195 @@ +# Formularze SoftSPM - konfiguracja i uruchomienie + +Wszystkie formularze strony (newsletter PL, kontakt EN, zgłoszenie na szkolenie, +rozmowa konsultingowa) wysyłają dane przez [send.php](send.php), który dopiero +woła `https://api.softspm.pl/send_email_api.php` zgodnie z +[doc/manual_send_email.html](../doc/manual_send_email.html). + +**Dlaczego przez pośrednika, a nie prosto z przeglądarki:** endpoint autoryzuje +się kluczem API w nagłówku `Authorization`. Klucz wysłany z JavaScriptu byłby +widoczny w źródle strony dla każdego, kto naciśnie Ctrl+U - i każdy mógłby nim +wysyłać maile na dowolny adres. Klucz zostaje na serwerze; przeglądarka rozmawia +wyłącznie z `send.php`. + +Wymagania: **PHP 7.4 lub nowszy**, rozszerzenie `curl` (jest fallback na +`file_get_contents`, ale cURL jest zalecany). + +--- + +## 1. Co dopisać w `/etc/hydronexis/config.php` + +`api/config.php` czyta ten plik automatycznie. Obsługiwane są dwa zapisy - +wybierz ten, który pasuje do tego, co już masz w pliku. + +### Wariant A - plik definiuje stałe (najczęstszy) + +```php +// --- formularze softspm.com ------------------------------------------- +define('SOFTSPM_MAIL_API_KEY', 'tutaj_klucz_do_send_email_api'); +define('SOFTSPM_FORM_SECRET', 'tutaj_losowy_ciag_min_32_znaki'); +define('SOFTSPM_FORM_RECIPIENT', 'pzators@go2.pl'); +``` + +### Wariant B - plik zwraca tablicę (`return [...]`) + +```php +return [ + // ... to, co już tam jest ... + + // --- formularze softspm.com --------------------------------------- + 'softspm_mail_api_key' => 'tutaj_klucz_do_send_email_api', + 'softspm_form_secret' => 'tutaj_losowy_ciag_min_32_znaki', + 'softspm_form_recipient' => 'pzators@go2.pl', +]; +``` + +### Wpisy opcjonalne + +```php +// Domeny, z których wolno wysyłać formularze. Bez tego wpisu obowiązuje +// lista z api/config.php (softspm.com, www.softspm.com, localhost). +define('SOFTSPM_FORM_HOSTS', ['softspm.com', 'www.softspm.com']); + +// Osobna skrzynka dla wybranego formularza. Identyfikatory formularzy: +// newsletter, newsletter-en, szkolenia, konsulting +define('SOFTSPM_FORM_RECIPIENTS', ['szkolenia' => 'szkolenia@softspm.com']); +``` + +### Skąd wziąć `SOFTSPM_FORM_SECRET` + +To nie jest klucz do niczego zewnętrznego - to sekret, którym `send.php` +podpisuje własne tokeny antybotowe. Wygeneruj dowolny losowy ciąg: + +```bash +php -r "echo bin2hex(random_bytes(32)), PHP_EOL;" +``` + +Zmiana tej wartości unieważnia formularze otwarte w tej chwili w +przeglądarkach; nic poza tym się nie dzieje. + +### Uwagi + +- Plik musi być czytelny dla użytkownika, na którym działa PHP (zwykle + `www-data`), i **nieczytelny dla świata**: `chmod 640`, właściciel + `root:www-data`. +- `api/config.php` robi na tym pliku `require`, więc plik musi być poprawnym + PHP. Jeśli wykonuje jakieś operacje przy dołączeniu (łączenie z bazą i tym + podobne), wykonają się także przy wysyłce formularza. +- Ścieżkę można zmienić zmienną środowiskową `SOFTSPM_SHARED_CONFIG`. + +## 2. Kolejność szukania ustawień + +Wygrywa pierwsze znalezione: + +1. zmienne środowiskowe `SOFTSPM_MAIL_API_KEY`, `SOFTSPM_FORM_SECRET` +2. `/etc/hydronexis/config.php` (stałe albo klucze tablicy - patrz wyżej) +3. `api/config.local.php` - tylko do pracy lokalnej, wzór w + [config.local.sample.php](config.local.sample.php) + +Adresat (`recipient`) i lista domen działają tak samo, z tym że wartość +z `/etc/hydronexis/config.php` nadpisuje domyślną z `api/config.php`. + +## 3. Katalog roboczy + +`send.php` zakłada przy pierwszym uruchomieniu `api/storage/` na zużyte tokeny, +liczniki adresów IP i log. Katalog dostaje `.htaccess` z `Require all denied`. +Na serwerze innym niż Apache **zablokuj ten katalog samodzielnie** - nie +powinien być dostępny z zewnątrz. Musi być zapisywalny dla PHP: + +```bash +mkdir -p /sciezka/do/strony/api/storage +chown www-data:www-data /sciezka/do/strony/api/storage +chmod 750 /sciezka/do/strony/api/storage +``` + +W logu (`api/storage/log/RRRR-MM.log`) zapisujemy tylko fakt wysyłki, nazwę +formularza i skrót adresu IP - bez treści zgłoszeń i bez danych osobowych. + +## 4. Format wiadomości: czysty tekst, nie HTML + +`send_email_api.php` **sanityzuje treść** przed osadzeniem jej we własnym +szablonie (manual, punkt 7). Znaczniki wysłane z `send.php` docierają więc do +skrzynki jako widoczny tekst `<p style="...">`. Dlatego wiadomości budujemy +z czystego tekstu i **nie wolno tu wstawiać HTML-a**. + +Układ jest odporny na to, czy szablon po drugiej stronie zamienia znaki nowej +linii na `<br>`, czy nie: każda pozycja to osobna linia `Etykieta: wartość`, +która zostaje czytelna nawet po sklejeniu w jeden akapit. + +``` +Nowe zgłoszenie ze strony softspm.com +Formularz: Zgłoszenie - szkolenia i webinaria +Odpowiedz na: jan.kowalski@pwik.example.pl + +--- DANE Z FORMULARZA --- +Imię i nazwisko: Jan Kowalski +Przedsiębiorstwo: PWiK Testowo +E-mail: jan.kowalski@pwik.example.pl +Telefon: 600 100 200 +Interesujący temat: Prawo wodne i wniosek taryfowy +Wiadomość: + Sześć osób z działu rozliczeń. + Najlepiej pierwszy kwartał. + +--- ZGODA UDZIELONA PRZY WYSYŁCE --- +Wyrażam zgodę na przetwarzanie moich danych osobowych przez SoftSPM (...) + +--- METRYKA --- +Strona: https://softspm.com/szkolenia.html#zgloszenie +Data (UTC): 2026-09-01 09:12:33 +Adres IP: 203.0.113.7 +``` + +Jeśli w otrzymanym mailu linie nadal są sklejone w jeden akapit, znaczy to, że +szablon endpointu nie zamienia `\n` na `<br>`. `send_email_api.php` jest wasz, +więc najczystszą poprawką jest tam jedno `nl2br()` na sanityzowanej treści - +nic po stronie tej strony nie wymaga wtedy zmian. + +## 5. Zabezpieczenia przed botami + +Suwak sam z siebie niczego nie gwarantuje - jest elementem interfejsu, a nie +kryptografią. Blokady, które faktycznie odsiewają automaty, działają na +serwerze i składają się na całość: + +| Warstwa | Co robi | +|---|---| +| Token podpisany HMAC | Wysyłka wymaga tokenu wydanego wcześniej przez `send.php`. Skrypt musi wykonać dwa dodatkowe żądania, nie jeden POST. | +| Potwierdzenie suwakiem | Osobne, podpisane potwierdzenie wydawane dopiero po przesunięciu suwaka; musi mieć ten sam identyfikator co token. | +| Jednorazowość | Każdy token przechodzi raz. Powtórka tego samego żądania dostaje HTTP 409. | +| Bramka czasowa | Minimum 3 sekundy między pierwszym dotknięciem formularza a wysyłką (`min_fill_seconds`). Automat robi to w ułamku sekundy. | +| Pole-pułapka | Ukryte pole `firma_www`. Wypełnione = bot. Odpowiadamy sukcesem, żeby nie podpowiadać, co go zdradziło. | +| Kontrola pochodzenia | `Origin`/`Referer` musi wskazywać na domenę z listy. | +| Limit adresu IP | 5 zgłoszeń na godzinę, 20 na dobę (`rate_limit` w `config.php`). | +| Stały adresat | Adres odbiorcy pochodzi wyłącznie z konfiguracji serwera, nigdy z formularza - endpoint nie jest przekaźnikiem spamu. | + +Jeśli mimo to zacznie przechodzić spam, następnym krokiem jest Cloudflare +Turnstile albo hCaptcha - obie działają na tej samej zasadzie co obecny token, +więc podmiana dotknie tylko `send.php` i `assets/forms.js`. + +## 6. Test po wdrożeniu + +```bash +# 1. Token - powinien zwrócić {"ok":true,"token":"t.szkolenia...."} +curl -s -H "Referer: https://softspm.com/szkolenia.html" \ + "https://softspm.com/api/send.php?action=token&form=szkolenia" + +# 2. Bez nagłówka Referer/Origin - powinno być HTTP 403 +curl -s -o /dev/null -w '%{http_code}\n' \ + "https://softspm.com/api/send.php?action=token&form=szkolenia" + +# 3. GET na nieznany formularz - HTTP 400 +curl -s -o /dev/null -w '%{http_code}\n' -H "Referer: https://softspm.com/" \ + "https://softspm.com/api/send.php?action=token&form=cokolwiek" + +# 4. POST bez tokenu - HTTP 400 (bad_token) +curl -s -H "Content-Type: application/json" -H "Origin: https://softspm.com" \ + -d '{"form":"szkolenia","zgoda":true,"imie":"Test"}' \ + "https://softspm.com/api/send.php" +``` + +Jeśli krok 1 zwraca `{"error":"not_configured"}`, PHP nie widzi klucza albo +sekretu - sprawdź uprawnienia `/etc/hydronexis/config.php`. + +Test całości: otwórz formularz w przeglądarce, wypełnij, przesuń suwak, +zaznacz zgodę i wyślij. Wiadomość powinna dojść na adres z +`SOFTSPM_FORM_RECIPIENT`, a w `api/storage/log/` pojawi się wpis `sent`. diff --git a/api/config.local.sample.php b/api/config.local.sample.php new file mode 100644 index 0000000..8035dfc --- /dev/null +++ b/api/config.local.sample.php @@ -0,0 +1,30 @@ +<?php +declare(strict_types=1); + +/** + * WZÓR pliku z sekretami - do pracy lokalnej (XAMPP, dev). + * + * NA PRODUKCJI TEN PLIK NIE JEST POTRZEBNY. Tam klucze bierzemy + * z /etc/hydronexis/config.php - patrz komentarz w config.php. + * + * 1. Skopiuj ten plik jako config.local.php (w tym samym katalogu). + * 2. Wpisz klucz API otrzymany do send_email_api.php. + * 3. Wygeneruj losowy token_secret, np.: + * php -r "echo bin2hex(random_bytes(32)), PHP_EOL;" + * 4. Nie dodawaj config.local.php do repozytorium ani do backupu publicznego. + */ + +return [ + 'api_key' => 'TUTAJ_KLUCZ_API', + 'token_secret' => 'TUTAJ_LOSOWY_CIAG_MIN_32_ZNAKI', + + // Adresat zgłoszeń ze wszystkich formularzy + 'recipient' => 'pzators@go2.pl', + + // Opcjonalnie: osobna skrzynka dla wybranego formularza + // Identyfikatory: newsletter, newsletter-en, szkolenia, konsulting + // 'recipients' => ['szkolenia' => 'szkolenia@softspm.com'], + + // Domeny, z których wolno wysyłać formularze + // 'allowed_hosts' => ['softspm.com', 'www.softspm.com', 'localhost'], +]; diff --git a/api/config.php b/api/config.php new file mode 100644 index 0000000..31245d2 --- /dev/null +++ b/api/config.php @@ -0,0 +1,164 @@ +<?php +declare(strict_types=1); + +/** + * Ustawienia integracji formularzy z api.softspm.pl/send_email_api.php + * + * Sekretów NIE trzymamy w tym pliku ani nigdzie w katalogu strony. + * Kolejność szukania klucza API i sekretu podpisu (wygrywa pierwsze znalezione): + * + * 1. zmienne środowiskowe SOFTSPM_MAIL_API_KEY i SOFTSPM_FORM_SECRET + * 2. wspólna konfiguracja serwera: /etc/hydronexis/config.php + * - stałe SOFTSPM_MAIL_API_KEY / SOFTSPM_FORM_SECRET, albo + * - klucze softspm_mail_api_key / softspm_form_secret w zwracanej tablicy + * 3. api/config.local.php (wzór: config.local.sample.php) - na potrzeby dev + * + * Ścieżkę wspólnej konfiguracji można zmienić zmienną SOFTSPM_SHARED_CONFIG. + */ + +$config = [ + // --- API wysyłki (patrz doc/manual_send_email.html) --------------------- + 'api_url' => 'https://api.softspm.pl/send_email_api.php', + 'api_key' => '', + 'timeout' => 15, + + // --- Sekret do podpisywania tokenów antybotowych (HMAC-SHA256) --------- + // Dowolny losowy ciąg, min. 32 znaki. Zmiana unieważnia otwarte formularze. + 'token_secret' => '', + + // --- Adresat zgłoszeń -------------------------------------------------- + // Ustawiany wyłącznie po stronie serwera. Nigdy nie pochodzi z formularza - + // inaczej endpoint stałby się otwartym przekaźnikiem spamu. + // Wartość domyślna; produkcja nadpisuje ją z /etc/hydronexis/config.php. + 'recipient' => 'pzators@go2.pl', + // Opcjonalnie: inny adres dla wybranego formularza, np. + // 'recipients' => ['szkolenia' => 'szkolenia@softspm.com'], + 'recipients' => [], + 'subject_prefix' => '[softspm.com]', + + // --- Skąd wolno wysyłać (nagłówek Origin / Referer) -------------------- + 'allowed_hosts' => [ + 'softspm.com', + 'www.softspm.com', + 'localhost', + '127.0.0.1', + ], + + // --- Zabezpieczenia ---------------------------------------------------- + // Minimalny czas od pierwszej interakcji z formularzem do wysyłki. + // Człowiek czyta, pisze i przesuwa suwak; skrypt robi wszystko w jednej serii. + 'min_fill_seconds' => 3, + // Minimalny odstęp między pobraniem tokenu a potwierdzeniem suwakiem. + // 0 = wyłączone. Podnoś ostrożnie: ktoś, kto od razu chwyta suwak, robi + // obie rzeczy w tej samej sekundzie i zostałby odrzucony niesłusznie. + 'min_slide_seconds' => 0, + // Po tylu sekundach token formularza wygasa (otwarta karta w tle). + 'max_token_age' => 7200, + // Po tylu sekundach wygasa samo potwierdzenie suwakiem. + 'max_proof_age' => 1800, + // Limit zgłoszeń z jednego adresu IP. + 'rate_limit' => [ + 'per_hour' => 5, + 'per_day' => 20, + ], + + // --- Dane robocze (zużyte tokeny, liczniki, log) ------------------------ + 'storage_dir' => __DIR__ . '/storage', +]; + +// --- 1. Zmienne środowiskowe ------------------------------------------------ +$fromEnv = [ + 'api_key' => getenv('SOFTSPM_MAIL_API_KEY'), + 'token_secret' => getenv('SOFTSPM_FORM_SECRET'), +]; +foreach ($fromEnv as $key => $value) { + if (is_string($value) && $value !== '') { + $config[$key] = $value; + } +} + +// --- 2. Wspólna konfiguracja serwera --------------------------------------- +$sharedPath = getenv('SOFTSPM_SHARED_CONFIG'); +if (!is_string($sharedPath) || $sharedPath === '') { + $sharedPath = '/etc/hydronexis/config.php'; +} + +if (is_file($sharedPath) && is_readable($sharedPath)) { + // Plik wczytujemy w osobnym zasięgu, żeby nie mieszał w zmiennych tego pliku. + $shared = (static function (string $file) { + /** @psalm-suppress UnresolvableInclude */ + return require $file; + })($sharedPath); + + if (!is_array($shared)) { + $shared = []; + } + + /** + * Jedna wartość może przyjść na dwa sposoby: jako stała zdefiniowana + * w /etc/hydronexis/config.php albo jako klucz w tablicy zwracanej przez + * ten plik. Zmienna środowiskowa ustawiona wcześniej ma pierwszeństwo. + */ + $pick = static function (string $constant, string $key) use ($shared) { + if (defined($constant)) { + $value = constant($constant); + if (is_string($value) && $value !== '') { + return $value; + } + if (is_array($value) && $value !== []) { + return $value; + } + } + if (isset($shared[$key]) && (is_string($shared[$key]) || is_array($shared[$key])) && $shared[$key] !== '') { + return $shared[$key]; + } + if (isset($shared[$constant]) && (is_string($shared[$constant]) || is_array($shared[$constant])) && $shared[$constant] !== '') { + return $shared[$constant]; + } + + return null; + }; + + // Sekrety - tylko jeśli nie przyszły ze zmiennych środowiskowych. + if ($config['api_key'] === '') { + $value = $pick('SOFTSPM_MAIL_API_KEY', 'softspm_mail_api_key'); + if (is_string($value)) { + $config['api_key'] = $value; + } + } + if ($config['token_secret'] === '') { + $value = $pick('SOFTSPM_FORM_SECRET', 'softspm_form_secret'); + if (is_string($value)) { + $config['token_secret'] = $value; + } + } + + // Adresat zgłoszeń - jeden dla wszystkich formularzy. + $value = $pick('SOFTSPM_FORM_RECIPIENT', 'softspm_form_recipient'); + if (is_string($value)) { + $config['recipient'] = $value; + } + + // Opcjonalnie: adresaci per formularz, tablica id_formularza => adres. + $value = $pick('SOFTSPM_FORM_RECIPIENTS', 'softspm_form_recipients'); + if (is_array($value)) { + $config['recipients'] = $value; + } + + // Opcjonalnie: domeny, z których wolno wysyłać. + $value = $pick('SOFTSPM_FORM_HOSTS', 'softspm_form_hosts'); + if (is_array($value) && $value !== []) { + $config['allowed_hosts'] = $value; + } +} + +// --- 3. Konfiguracja lokalna (dev) ------------------------------------------ +$local = __DIR__ . '/config.local.php'; +if (is_file($local)) { + $override = require $local; + if (is_array($override)) { + $config = array_replace($config, $override); + } +} + +return $config; diff --git a/api/send.php b/api/send.php new file mode 100644 index 0000000..ff4a3de --- /dev/null +++ b/api/send.php @@ -0,0 +1,632 @@ +<?php +declare(strict_types=1); + +/** + * SoftSPM - obsługa formularzy strony. + * + * Pośrednik między formularzami a api.softspm.pl/send_email_api.php. + * Klucz API zostaje na serwerze; przeglądarka nigdy go nie widzi. Adresat + * jest stały (config.php), więc endpointu nie da się użyć do wysyłki + * dowolnej wiadomości pod dowolny adres. + * + * Tryby pracy: + * GET ?action=token&form=<id> -> wydaje podpisany token formularza + * GET ?action=confirm&form=<id>&token=.. -> potwierdza przesunięcie suwaka + * POST (JSON) -> waliduje i wysyła zgłoszenie + */ + +$config = require __DIR__ . '/config.php'; + +header('Content-Type: application/json; charset=utf-8'); +header('X-Content-Type-Options: nosniff'); +header('Cache-Control: no-store'); +header('Referrer-Policy: same-origin'); + +// --------------------------------------------------------------------------- +// Definicje formularzy. Serwer przyjmuje wyłącznie te pola - cokolwiek innego +// przyjdzie w JSON-ie, jest ignorowane. +// --------------------------------------------------------------------------- +function spm_forms(): array +{ + return [ + 'newsletter' => [ + 'lang' => 'pl', + 'label' => 'Newsletter', + 'subject' => 'Zapis do newslettera', + 'fields' => [ + 'email' => ['label' => 'E-mail', 'type' => 'email', 'required' => true, 'max' => 190], + ], + ], + 'newsletter-en' => [ + 'lang' => 'en', + 'label' => 'Newsletter (EN)', + 'subject' => 'Kontakt z wersji angielskiej', + 'fields' => [ + 'email' => ['label' => 'E-mail', 'type' => 'email', 'required' => true, 'max' => 190], + ], + ], + 'szkolenia' => [ + 'lang' => 'pl', + 'label' => 'Zgłoszenie - szkolenia i webinaria', + 'subject' => 'Zapytanie o szkolenie', + 'fields' => [ + 'imie' => ['label' => 'Imię i nazwisko', 'type' => 'text', 'required' => true, 'max' => 120], + 'firma' => ['label' => 'Przedsiębiorstwo', 'type' => 'text', 'required' => true, 'max' => 160], + 'email' => ['label' => 'E-mail', 'type' => 'email', 'required' => true, 'max' => 190], + 'telefon' => ['label' => 'Telefon', 'type' => 'text', 'required' => false, 'max' => 40], + 'temat' => ['label' => 'Interesujący temat', 'type' => 'text', 'required' => false, 'max' => 160], + 'wiadomosc' => ['label' => 'Wiadomość', 'type' => 'textarea', 'required' => false, 'max' => 4000], + ], + ], + 'konsulting' => [ + 'lang' => 'pl', + 'label' => 'Zgłoszenie - konsulting', + 'subject' => 'Prośba o rozmowę wstępną', + 'fields' => [ + 'imie' => ['label' => 'Imię i nazwisko', 'type' => 'text', 'required' => true, 'max' => 120], + 'firma' => ['label' => 'Przedsiębiorstwo', 'type' => 'text', 'required' => true, 'max' => 160], + 'email' => ['label' => 'E-mail', 'type' => 'email', 'required' => true, 'max' => 190], + 'telefon' => ['label' => 'Telefon', 'type' => 'text', 'required' => false, 'max' => 40], + 'zakres' => ['label' => 'Interesujący zakres', 'type' => 'text', 'required' => false, 'max' => 160], + 'wiadomosc' => ['label' => 'Opis sytuacji', 'type' => 'textarea', 'required' => false, 'max' => 4000], + ], + ], + ]; +} + +// Treść zgody utrwalana razem ze zgłoszeniem - dowód, na co konkretnie +// zgodził się nadawca w dniu wysyłki. +function spm_consent_text(string $lang): string +{ + if ($lang === 'en') { + return 'I consent to SoftSPM processing my personal data in order to be contacted ' + . 'about commercial and marketing matters, including offers of training, ' + . 'consulting and software. I may withdraw this consent at any time ' + . 'by writing to info@softspm.com.'; + } + + return 'Wyrażam zgodę na przetwarzanie moich danych osobowych przez SoftSPM w celu ' + . 'kontaktu handlowego i marketingowego, w tym przedstawienia oferty szkoleń, ' + . 'konsultingu i aplikacji. Zgodę mogę wycofać w każdej chwili, pisząc na ' + . 'info@softspm.com.'; +} + +// --------------------------------------------------------------------------- +// Narzędzia +// --------------------------------------------------------------------------- +function spm_respond(int $status, array $payload): void +{ + http_response_code($status); + echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + exit; +} + +function spm_fail(int $status, string $code, string $message): void +{ + spm_respond($status, ['ok' => false, 'error' => $code, 'message' => $message]); +} + +function spm_b64u(string $bin): string +{ + return rtrim(strtr(base64_encode($bin), '+/', '-_'), '='); +} + +function spm_sign(string $payload, string $secret): string +{ + return spm_b64u(hash_hmac('sha256', $payload, $secret, true)); +} + +/** Token ma postać: rodzaj.formularz.nonce.czas.podpis */ +function spm_issue_token(string $kind, string $form, string $nonce, string $secret): string +{ + $payload = $kind . '.' . $form . '.' . $nonce . '.' . time(); + + return $payload . '.' . spm_sign($payload, $secret); +} + +/** @return array{nonce:string, ts:int}|null */ +function spm_read_token(string $token, string $kind, string $form, string $secret): ?array +{ + $parts = explode('.', $token); + if (count($parts) !== 5) { + return null; + } + [$gotKind, $gotForm, $nonce, $ts, $sig] = $parts; + if ($gotKind !== $kind || $gotForm !== $form || !ctype_digit($ts)) { + return null; + } + if (preg_match('/^[A-Za-z0-9_-]{8,64}$/', $nonce) !== 1) { + return null; + } + $payload = $gotKind . '.' . $gotForm . '.' . $nonce . '.' . $ts; + if (!hash_equals(spm_sign($payload, $secret), $sig)) { + return null; + } + + return ['nonce' => $nonce, 'ts' => (int) $ts]; +} + +function spm_storage(array $config, string $sub): string +{ + $dir = rtrim($config['storage_dir'], '/\\') . '/' . $sub; + if (!is_dir($dir)) { + @mkdir($dir, 0770, true); + } + // Katalog roboczy nie ma prawa być czytany przez przeglądarkę. + $guard = rtrim($config['storage_dir'], '/\\') . '/.htaccess'; + if (!is_file($guard)) { + @file_put_contents($guard, "Require all denied\n<IfModule !mod_authz_core.c>\nDeny from all\n</IfModule>\n"); + } + + return $dir; +} + +/** Jednorazowość tokenu: nonce raz zużyty nie przechodzi drugi raz. */ +function spm_nonce_used(array $config, string $nonce): bool +{ + $dir = spm_storage($config, 'nonce'); + $file = $dir . '/' . hash('sha256', $nonce) . '.used'; + if (is_file($file)) { + return true; + } + @file_put_contents($file, (string) time()); + + // Sprzątanie: co jakiś czas kasujemy wpisy starsze niż czas życia tokenu. + if (random_int(1, 25) === 1) { + $limit = time() - (int) $config['max_token_age'] - 3600; + foreach ((glob($dir . '/*.used') ?: []) as $old) { + if (@filemtime($old) < $limit) { + @unlink($old); + } + } + } + + return false; +} + +function spm_client_ip(): string +{ + $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; + + return is_string($ip) ? $ip : '0.0.0.0'; +} + +/** Limit zgłoszeń z jednego adresu IP - godzinowy i dobowy. */ +function spm_rate_limited(array $config, string $ip): bool +{ + $dir = spm_storage($config, 'rate'); + $file = $dir . '/' . hash('sha256', $ip . '|' . $config['token_secret']) . '.json'; + $now = time(); + + $stamps = []; + if (is_file($file)) { + $raw = @file_get_contents($file); + if (is_string($raw) && $raw !== '') { + $decoded = json_decode($raw, true); + if (is_array($decoded)) { + $stamps = array_values(array_filter($decoded, static fn($t) => is_int($t) && $t > $now - 86400)); + } + } + } + + $lastHour = count(array_filter($stamps, static fn(int $t) => $t > $now - 3600)); + if ($lastHour >= (int) $config['rate_limit']['per_hour'] || count($stamps) >= (int) $config['rate_limit']['per_day']) { + return true; + } + + $stamps[] = $now; + @file_put_contents($file, json_encode($stamps), LOCK_EX); + + return false; +} + +/** Zgłoszenie musi pochodzić ze strony SoftSPM, nie z obcego skryptu. */ +function spm_origin_ok(array $config): bool +{ + $candidates = []; + foreach (['HTTP_ORIGIN', 'HTTP_REFERER'] as $header) { + if (!empty($_SERVER[$header]) && is_string($_SERVER[$header])) { + $host = parse_url($_SERVER[$header], PHP_URL_HOST); + if (is_string($host) && $host !== '') { + $candidates[] = strtolower($host); + } + } + } + if ($candidates === []) { + return false; + } + $allowed = array_map('strtolower', (array) $config['allowed_hosts']); + foreach ($candidates as $host) { + if (in_array($host, $allowed, true)) { + return true; + } + } + + return false; +} + +function spm_log(array $config, string $line): void +{ + $dir = spm_storage($config, 'log'); + @file_put_contents( + $dir . '/' . gmdate('Y-m') . '.log', + gmdate('c') . ' ' . $line . "\n", + FILE_APPEND | LOCK_EX + ); +} + +/** Pojedyncza linia tekstu: bez znaków sterujących, bez łamania wiersza. */ +function spm_clean_line(string $value): string +{ + $value = str_replace(["\r", "\n", "\t"], ' ', $value); + $value = preg_replace('/[\x00-\x1F\x7F]/u', '', $value) ?? ''; + + return trim(preg_replace('/\s{2,}/u', ' ', $value) ?? ''); +} + +function spm_clean_text(string $value): string +{ + $value = str_replace("\r\n", "\n", $value); + $value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $value) ?? ''; + + return trim($value); +} + +// --------------------------------------------------------------------------- +// Start obsługi żądania +// --------------------------------------------------------------------------- +$method = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET'); + +if ($method === 'OPTIONS') { + http_response_code(204); + exit; +} + +if ($config['token_secret'] === '' || $config['api_key'] === '') { + spm_log($config, 'config-missing'); + spm_fail(503, 'not_configured', 'Formularz nie jest jeszcze skonfigurowany. Napisz na info@softspm.com.'); +} + +$forms = spm_forms(); + +// --- GET: wydanie tokenu i potwierdzenie suwaka ---------------------------- +if ($method === 'GET') { + $action = isset($_GET['action']) && is_string($_GET['action']) ? $_GET['action'] : ''; + $form = isset($_GET['form']) && is_string($_GET['form']) ? $_GET['form'] : ''; + + if (!isset($forms[$form])) { + spm_fail(400, 'unknown_form', 'Nieznany formularz.'); + } + if (!spm_origin_ok($config)) { + spm_fail(403, 'bad_origin', 'Żądanie spoza strony SoftSPM.'); + } + + if ($action === 'token') { + $nonce = spm_b64u(random_bytes(12)); + spm_respond(200, [ + 'ok' => true, + 'token' => spm_issue_token('t', $form, $nonce, $config['token_secret']), + ]); + } + + if ($action === 'confirm') { + $token = isset($_GET['token']) && is_string($_GET['token']) ? $_GET['token'] : ''; + $data = spm_read_token($token, 't', $form, $config['token_secret']); + if ($data === null) { + spm_fail(400, 'bad_token', 'Token formularza jest nieprawidłowy. Odśwież stronę.'); + } + if (time() - $data['ts'] > (int) $config['max_token_age']) { + spm_fail(400, 'expired', 'Formularz był otwarty zbyt długo. Odśwież stronę.'); + } + spm_respond(200, [ + 'ok' => true, + 'proof' => spm_issue_token('c', $form, $data['nonce'], $config['token_secret']), + ]); + } + + spm_fail(400, 'bad_action', 'Nieznana operacja.'); +} + +if ($method !== 'POST') { + spm_fail(405, 'method_not_allowed', 'Dozwolone są wyłącznie żądania POST.'); +} + +// --- POST: właściwa wysyłka ------------------------------------------------- + +// Formularz wysłany bez JavaScriptu leci tu jako zwykły POST przeglądarki. +// Zamiast JSON-a z błędem pokazujemy stronę, którą da się przeczytać. +$contentType = strtolower((string) ($_SERVER['CONTENT_TYPE'] ?? '')); +if (strpos($contentType, 'application/json') === false) { + http_response_code(400); + header('Content-Type: text/html; charset=utf-8'); + echo '<!doctype html><html lang="pl"><head><meta charset="utf-8">' + . '<title>Formularz SoftSPM' + . '

Formularz wymaga włączonego JavaScriptu

' + . '

Napisz do nas bezpośrednio na info@softspm.com. ' + . 'Przepraszamy za kłopot.

' + . '

Wróć do formularza

' + . ''; + exit; +} + +$raw = file_get_contents('php://input'); +if (!is_string($raw) || trim($raw) === '') { + spm_fail(400, 'empty_payload', 'Puste żądanie.'); +} + +$input = json_decode($raw, true); +if (!is_array($input)) { + spm_fail(400, 'invalid_json', 'Nieprawidłowy format danych.'); +} + +$form = isset($input['form']) && is_string($input['form']) ? $input['form'] : ''; +if (!isset($forms[$form])) { + spm_fail(400, 'unknown_form', 'Nieznany formularz.'); +} +$def = $forms[$form]; +$lang = $def['lang']; +$isEn = $lang === 'en'; + +if (!spm_origin_ok($config)) { + spm_log($config, 'reject origin form=' . $form); + spm_fail(403, 'bad_origin', $isEn ? 'Request did not come from the SoftSPM site.' : 'Żądanie spoza strony SoftSPM.'); +} + +// Pułapka na boty: pole ukryte przed człowiekiem. Wypełnione = automat. +// Odpowiadamy sukcesem, żeby nie podpowiadać botowi, co go zdradziło. +$trap = isset($input['firma_www']) && is_string($input['firma_www']) ? trim($input['firma_www']) : ''; +if ($trap !== '') { + spm_log($config, 'trap form=' . $form . ' ip=' . hash('sha256', spm_client_ip())); + spm_respond(200, ['ok' => true, 'message' => $isEn ? 'Message sent.' : 'Wiadomość wysłana.']); +} + +// Token formularza + potwierdzenie suwakiem. +$token = isset($input['token']) && is_string($input['token']) ? $input['token'] : ''; +$proof = isset($input['proof']) && is_string($input['proof']) ? $input['proof'] : ''; +$tokenData = spm_read_token($token, 't', $form, $config['token_secret']); +$proofData = spm_read_token($proof, 'c', $form, $config['token_secret']); + +if ($tokenData === null || $proofData === null || !hash_equals($tokenData['nonce'], $proofData['nonce'])) { + spm_log($config, 'reject token form=' . $form); + spm_fail(400, 'bad_token', $isEn + ? 'Verification failed. Refresh the page and slide the control again.' + : 'Weryfikacja nie powiodła się. Odśwież stronę i przesuń suwak ponownie.'); +} + +$now = time(); +if ($now - $tokenData['ts'] > (int) $config['max_token_age'] || $now - $proofData['ts'] > (int) $config['max_proof_age']) { + spm_fail(400, 'expired', $isEn + ? 'The form has expired. Refresh the page and try again.' + : 'Formularz stracił ważność. Odśwież stronę i spróbuj ponownie.'); +} +$minSlide = (int) $config['min_slide_seconds']; +if ($now - $tokenData['ts'] < (int) $config['min_fill_seconds'] + || $proofData['ts'] < $tokenData['ts'] + || ($minSlide > 0 && $proofData['ts'] - $tokenData['ts'] < $minSlide)) { + spm_log($config, 'reject speed form=' . $form); + spm_fail(429, 'too_fast', $isEn + ? 'That was too quick. Try again in a moment.' + : 'Zgłoszenie wysłano zbyt szybko. Spróbuj ponownie za chwilę.'); +} +if (spm_nonce_used($config, $tokenData['nonce'])) { + spm_fail(409, 'already_sent', $isEn + ? 'This form has already been sent. Refresh the page to send another message.' + : 'To zgłoszenie zostało już wysłane. Odśwież stronę, aby wysłać kolejne.'); +} + +// Zgoda marketingowa - wymagana, sprawdzana także po stronie serwera. +$consent = $input['zgoda'] ?? false; +if ($consent !== true && $consent !== 'true' && $consent !== 1 && $consent !== '1' && $consent !== 'on') { + spm_fail(400, 'consent_required', $isEn + ? 'Consent to be contacted is required.' + : 'Zgoda na kontakt jest wymagana.'); +} + +if (spm_rate_limited($config, spm_client_ip())) { + spm_log($config, 'reject rate form=' . $form . ' ip=' . hash('sha256', spm_client_ip())); + spm_fail(429, 'rate_limited', $isEn + ? 'Too many messages from this address. Please write to info@softspm.com.' + : 'Zbyt wiele zgłoszeń z tego adresu. Napisz na info@softspm.com.'); +} + +// --- Walidacja pól ---------------------------------------------------------- +$values = []; +$replyTo = ''; +foreach ($def['fields'] as $name => $rule) { + $value = $input[$name] ?? ''; + if (!is_string($value)) { + $value = ''; + } + $value = $rule['type'] === 'textarea' ? spm_clean_text($value) : spm_clean_line($value); + + if ($value === '') { + if (!empty($rule['required'])) { + spm_fail(400, 'missing_field', $isEn + ? sprintf('Field "%s" is required.', $rule['label']) + : sprintf('Pole „%s" jest wymagane.', $rule['label'])); + } + $values[$name] = ''; + continue; + } + + if (mb_strlen($value) > (int) $rule['max']) { + spm_fail(400, 'field_too_long', $isEn + ? sprintf('Field "%s" is too long.', $rule['label']) + : sprintf('Pole „%s" jest za długie.', $rule['label'])); + } + + if ($rule['type'] === 'email') { + if (filter_var($value, FILTER_VALIDATE_EMAIL) === false) { + spm_fail(400, 'invalid_email', $isEn + ? 'The e-mail address is not valid.' + : 'Adres e-mail jest nieprawidłowy.'); + } + $replyTo = $value; + } + + $values[$name] = $value; +} + +// --- Budowa wiadomości ------------------------------------------------------ +// UWAGA: send_email_api.php sanityzuje treść przed osadzeniem jej we własnym +// szablonie HTML (manual, punkt 7). Znaczniki wysłane stąd dotarłyby do skrzynki +// jako widoczny tekst "

", więc wysyłamy czysty tekst. +// +// Nie wiemy, czy szablon po drugiej stronie zamienia znaki nowej linii na
, +// dlatego układ jest odporny na oba warianty: każda pozycja to osobna linia +// "Etykieta: wartość", która pozostaje czytelna nawet po sklejeniu w akapit. +$topic = ''; +foreach (['temat', 'zakres'] as $key) { + if (!empty($values[$key])) { + $topic = $values[$key]; + break; + } +} + +$subject = trim($config['subject_prefix'] . ' ' . $def['subject']); +if ($topic !== '') { + $subject .= ' - ' . $topic; +} +if (mb_strlen($subject) > 250) { + $subject = mb_substr($subject, 0, 247) . '...'; +} + +$lines = []; +$lines[] = $isEn ? 'New message from softspm.com' : 'Nowe zgłoszenie ze strony softspm.com'; +$lines[] = ($isEn ? 'Form: ' : 'Formularz: ') . $def['label']; +if ($replyTo !== '') { + $lines[] = ($isEn ? 'Reply to: ' : 'Odpowiedz na: ') . $replyTo; +} +$lines[] = ''; +$lines[] = $isEn ? '--- DETAILS ---' : '--- DANE Z FORMULARZA ---'; + +foreach ($def['fields'] as $name => $rule) { + $value = $values[$name] ?? ''; + if ($value === '') { + continue; + } + if ($rule['type'] === 'textarea') { + // Treść wieloliniowa dostaje własny blok, każdy wiersz z wcięciem, + // żeby po sklejeniu akapitu dało się odróżnić ją od pozostałych pól. + $lines[] = $rule['label'] . ':'; + foreach (explode("\n", $value) as $row) { + $lines[] = ' ' . $row; + } + continue; + } + $lines[] = $rule['label'] . ': ' . $value; +} + +$lines[] = ''; +$lines[] = $isEn ? '--- CONSENT GIVEN ON SUBMISSION ---' : '--- ZGODA UDZIELONA PRZY WYSYŁCE ---'; +$lines[] = spm_consent_text($lang); + +$lines[] = ''; +$lines[] = $isEn ? '--- METADATA ---' : '--- METRYKA ---'; +$lines[] = ($isEn ? 'Page: ' : 'Strona: ') + . (isset($input['page']) && is_string($input['page']) ? spm_clean_line(mb_substr($input['page'], 0, 300)) : '-'); +$lines[] = ($isEn ? 'Date (UTC): ' : 'Data (UTC): ') . gmdate('Y-m-d H:i:s'); +$lines[] = ($isEn ? 'IP address: ' : 'Adres IP: ') . spm_client_ip(); + +$message = implode("\n", $lines); + +// Zostawiamy zapas na szablon po stronie API (limit endpointu to 20000 znaków). +if (mb_strlen($message) > 15000) { + $message = mb_substr($message, 0, 15000) . "\n[...]"; +} + +// --- Wysyłka przez api.softspm.pl ------------------------------------------ +// Adresat pochodzi wyłącznie z konfiguracji serwera. Formularz może mieć +// własną skrzynkę (config['recipients']), inaczej idzie na adres domyślny. +$recipient = (string) $config['recipient']; +if (isset($config['recipients'][$form]) && is_string($config['recipients'][$form]) && $config['recipients'][$form] !== '') { + $recipient = $config['recipients'][$form]; +} +if (filter_var($recipient, FILTER_VALIDATE_EMAIL) === false) { + spm_log($config, 'config-bad-recipient form=' . $form); + spm_fail(503, 'not_configured', $isEn + ? 'The form is not configured yet. Please write to info@softspm.com.' + : 'Formularz nie jest jeszcze skonfigurowany. Napisz na info@softspm.com.'); +} + +$payload = json_encode([ + 'to' => $recipient, + 'subject' => $subject, + 'message' => $message, +], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + +if ($payload === false) { + spm_fail(500, 'encode_failed', $isEn ? 'Could not prepare the message.' : 'Nie udało się przygotować wiadomości.'); +} + +$headers = [ + 'Content-Type: application/json', + 'Authorization: Bearer ' . $config['api_key'], +]; + +$status = 0; +$response = ''; +$netError = ''; + +if (function_exists('curl_init')) { + $ch = curl_init($config['api_url']); + curl_setopt_array($ch, [ + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $payload, + CURLOPT_HTTPHEADER => $headers, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_CONNECTTIMEOUT => 8, + CURLOPT_TIMEOUT => (int) $config['timeout'], + ]); + $body = curl_exec($ch); + if ($body === false) { + $netError = curl_error($ch); + } else { + $response = (string) $body; + } + $status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE); + curl_close($ch); +} else { + $context = stream_context_create([ + 'http' => [ + 'method' => 'POST', + 'header' => implode("\r\n", $headers), + 'content' => $payload, + 'timeout' => (int) $config['timeout'], + 'ignore_errors' => true, + ], + ]); + $body = @file_get_contents($config['api_url'], false, $context); + if ($body === false) { + $netError = 'stream request failed'; + } else { + $response = (string) $body; + foreach ($http_response_header ?? [] as $line) { + if (preg_match('#^HTTP/\S+\s+(\d{3})#', $line, $m) === 1) { + $status = (int) $m[1]; + } + } + } +} + +if ($status !== 200) { + // Szczegóły zostają w logu serwera, klient dostaje komunikat bez detali. + spm_log($config, sprintf( + 'send-failed form=%s http=%d err=%s resp=%s', + $form, + $status, + $netError !== '' ? $netError : '-', + substr($response, 0, 300) + )); + spm_fail(502, 'send_failed', $isEn + ? 'The message could not be sent. Please write to info@softspm.com.' + : 'Nie udało się wysłać wiadomości. Napisz na info@softspm.com.'); +} + +spm_log($config, 'sent form=' . $form . ' ip=' . hash('sha256', spm_client_ip())); +spm_respond(200, [ + 'ok' => true, + 'message' => $isEn + ? 'Thank you - your message has been sent. We reply within one working day.' + : 'Dziękujemy - wiadomość została wysłana. Odpowiadamy w ciągu jednego dnia roboczego.', +]); diff --git a/assets/forms.js b/assets/forms.js new file mode 100644 index 0000000..99c3213 --- /dev/null +++ b/assets/forms.js @@ -0,0 +1,309 @@ +/* ============================================================================ + SoftSPM - obsługa formularzy + Suwak antybotowy, zgoda marketingowa i wysyłka przez api/send.php. + Czysty JavaScript, bez zależności. + ========================================================================= */ +(function () { + 'use strict'; + + var DICT = { + pl: { + slide: 'Przesuń, aby zweryfikować', + verified: 'Zweryfikowano', + checking: 'Sprawdzanie...', + needSlider: 'Najpierw przesuń suwak, aby potwierdzić, że nie jesteś robotem.', + needConsent: 'Zaznacz zgodę na kontakt, aby wysłać zgłoszenie.', + sending: 'Wysyłanie...', + sendLabel: 'Wyślij', + tokenError: 'Nie udało się przygotować formularza. Odśwież stronę.', + network: 'Brak połączenia z serwerem. Spróbuj ponownie lub napisz na info@softspm.com.', + generic: 'Nie udało się wysłać wiadomości. Napisz na info@softspm.com.', + handleLabel: 'Przesuń suwak w prawo, aby potwierdzić, że nie jesteś robotem' + }, + en: { + slide: 'Slide to verify', + verified: 'Verified', + checking: 'Checking...', + needSlider: 'Please slide the control first to confirm you are not a robot.', + needConsent: 'Please tick the consent box to send the message.', + sending: 'Sending...', + sendLabel: 'Send', + tokenError: 'The form could not be prepared. Please refresh the page.', + network: 'No connection to the server. Try again or write to info@softspm.com.', + generic: 'The message could not be sent. Please write to info@softspm.com.', + handleLabel: 'Slide the control to the right to confirm you are not a robot' + } + }; + + var lang = (document.documentElement.getAttribute('lang') || 'pl').slice(0, 2).toLowerCase(); + var t = DICT[lang] || DICT.pl; + + /* -- suwak weryfikacyjny -------------------------------------------------- */ + function Slider(root, onVerified) { + var track = root.querySelector('[data-verify-track]'); + var fill = root.querySelector('[data-verify-fill]'); + var handle = root.querySelector('[data-verify-handle]'); + var hint = root.querySelector('[data-verify-hint]'); + if (!track || !handle) { return null; } + + var pos = 0; + var done = false; + var busy = false; + var pointerId = null; + + handle.setAttribute('aria-label', t.handleLabel); + + function limit() { + return Math.max(0, track.clientWidth - handle.offsetWidth); + } + + function place(x) { + var max = limit(); + pos = Math.max(0, Math.min(max, x)); + handle.style.left = pos + 'px'; + if (fill) { fill.style.width = (pos + handle.offsetWidth) + 'px'; } + handle.setAttribute('aria-valuenow', max > 0 ? String(Math.round((pos / max) * 100)) : '0'); + } + + function reset() { + done = false; + busy = false; + root.classList.remove('is-verified'); + handle.setAttribute('aria-valuetext', t.slide); + if (hint) { hint.textContent = t.slide; } + place(0); + } + + function complete() { + if (done || busy) { return; } + busy = true; + place(limit()); + if (hint) { hint.textContent = t.checking; } + onVerified(function (ok) { + busy = false; + if (ok) { + done = true; + root.classList.add('is-verified'); + handle.setAttribute('aria-valuetext', t.verified); + if (hint) { hint.textContent = t.verified; } + } else { + reset(); + } + }); + } + + handle.addEventListener('pointerdown', function (event) { + if (done || busy) { return; } + pointerId = event.pointerId; + handle.setPointerCapture(pointerId); + root.classList.add('is-dragging'); + var offset = event.clientX - handle.getBoundingClientRect().left; + + function move(moveEvent) { + place(moveEvent.clientX - track.getBoundingClientRect().left - offset); + } + + function up() { + handle.removeEventListener('pointermove', move); + handle.removeEventListener('pointerup', up); + handle.removeEventListener('pointercancel', up); + root.classList.remove('is-dragging'); + if (pointerId !== null && handle.hasPointerCapture && handle.hasPointerCapture(pointerId)) { + handle.releasePointerCapture(pointerId); + } + pointerId = null; + if (pos >= limit() - 2) { complete(); } else { place(0); } + } + + handle.addEventListener('pointermove', move); + handle.addEventListener('pointerup', up); + handle.addEventListener('pointercancel', up); + event.preventDefault(); + }); + + // Obsługa klawiaturą - strzałkami do końca albo klawiszem End. + handle.addEventListener('keydown', function (event) { + if (done || busy) { return; } + var step = limit() / 4; + var key = event.key; + if (key === 'ArrowRight' || key === 'ArrowUp') { place(pos + step); } + else if (key === 'ArrowLeft' || key === 'ArrowDown') { place(pos - step); } + else if (key === 'End') { place(limit()); } + else if (key === 'Home') { place(0); } + else { return; } + event.preventDefault(); + if (pos >= limit() - 1) { complete(); } + }); + + window.addEventListener('resize', function () { + place(done ? limit() : pos); + }); + + reset(); + + return { + isVerified: function () { return done; }, + focus: function () { handle.focus(); }, + reset: reset + }; + } + + /* -- pojedynczy formularz ------------------------------------------------- */ + function initForm(form) { + var id = form.getAttribute('data-form'); + var endpoint = form.getAttribute('data-endpoint') || 'api/send.php'; + var status = form.querySelector('[data-form-status]'); + var button = form.querySelector('button[type="submit"]'); + var consent = form.querySelector('[data-consent]'); + var verifyRoot = form.querySelector('[data-verify]'); + var buttonLabel = button ? button.textContent : t.sendLabel; + + var token = null; + var proof = null; + var sending = false; + + function setStatus(text, kind) { + if (!status) { return; } + status.textContent = text || ''; + status.className = 'form-status' + (kind ? ' is-' + kind : ''); + } + + function request(params, done) { + var url = endpoint + '?' + params; + var xhrDone = false; + fetch(url, { credentials: 'same-origin', headers: { 'Accept': 'application/json' } }) + .then(function (response) { return response.json().catch(function () { return {}; }); }) + .then(function (data) { xhrDone = true; done(data && data.ok ? data : null); }) + .catch(function () { if (!xhrDone) { done(null); } }); + } + + // Token pobieramy dopiero, gdy ktoś zacznie korzystać z formularza. + // Dzięki temu serwer mierzy realny czas wypełniania. + // + // Kolejka pending jest istotna: zdarzenie na suwaku potrafi zawołać tę + // funkcję dwa razy w tej samej chwili. Bez niej powstałyby dwa tokeny, + // a potwierdzenie dotyczyłoby innego niż ten wysłany razem z formularzem. + var pending = null; + + function ensureToken(done) { + if (token) { done(true); return; } + if (pending) { pending.push(done); return; } + pending = [done]; + request('action=token&form=' + encodeURIComponent(id), function (data) { + var queue = pending; + pending = null; + if (data && data.token) { token = data.token; } + for (var i = 0; i < queue.length; i++) { queue[i](!!token); } + }); + } + + var slider = verifyRoot ? Slider(verifyRoot, function (finish) { + ensureToken(function (ok) { + if (!ok) { setStatus(t.tokenError, 'error'); finish(false); return; } + request('action=confirm&form=' + encodeURIComponent(id) + '&token=' + encodeURIComponent(token), function (data) { + if (data && data.proof) { + proof = data.proof; + setStatus('', null); + finish(true); + } else { + setStatus(t.tokenError, 'error'); + finish(false); + } + }); + }); + }) : null; + + // Token bierzemy przy pierwszym dotknięciu formularza - wtedy serwer mierzy + // realny czas wypełniania, a nie moment przesunięcia suwaka. + var wake = function () { ensureToken(function () {}); }; + form.addEventListener('focusin', wake); + if (verifyRoot) { + verifyRoot.addEventListener('pointerdown', wake); + verifyRoot.addEventListener('keydown', wake); + } + + form.addEventListener('submit', function (event) { + event.preventDefault(); + if (sending) { return; } + + if (slider && !slider.isVerified()) { + setStatus(t.needSlider, 'error'); + slider.focus(); + return; + } + if (consent && !consent.checked) { + setStatus(t.needConsent, 'error'); + consent.focus(); + return; + } + if (!token || !proof) { + setStatus(t.tokenError, 'error'); + return; + } + + var payload = { + form: id, + token: token, + proof: proof, + zgoda: true, + page: window.location.href + }; + var fields = form.querySelectorAll('input[name], select[name], textarea[name]'); + for (var i = 0; i < fields.length; i++) { + var field = fields[i]; + if (field.type === 'checkbox' || field.type === 'radio' || field.type === 'submit') { continue; } + payload[field.name] = field.value; + } + + sending = true; + setStatus(t.sending, 'busy'); + if (button) { button.disabled = true; button.textContent = t.sending; } + + fetch(endpoint, { + method: 'POST', + credentials: 'same-origin', + headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, + body: JSON.stringify(payload) + }) + .then(function (response) { + return response.json() + .catch(function () { return { ok: false }; }) + .then(function (data) { return { status: response.status, data: data }; }); + }) + .then(function (result) { + sending = false; + if (button) { button.disabled = false; button.textContent = buttonLabel; } + if (result.data && result.data.ok) { + form.reset(); + token = null; + proof = null; + if (slider) { slider.reset(); } + setStatus(result.data.message || '', 'ok'); + } else { + setStatus((result.data && result.data.message) || t.generic, 'error'); + // Token jest jednorazowy i serwer mógł go już zużyć, więc po każdym + // błędzie bierzemy nowy i prosimy o ponowne przesunięcie suwaka. + token = null; + proof = null; + if (slider) { slider.reset(); } + } + }) + .catch(function () { + sending = false; + if (button) { button.disabled = false; button.textContent = buttonLabel; } + setStatus(t.network, 'error'); + }); + }); + } + + function boot() { + var forms = document.querySelectorAll('form[data-form]'); + for (var i = 0; i < forms.length; i++) { initForm(forms[i]); } + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', boot); + } else { + boot(); + } +})(); diff --git a/assets/img/aplikacje.svg b/assets/img/aplikacje.svg new file mode 100644 index 0000000..0754df6 --- /dev/null +++ b/assets/img/aplikacje.svg @@ -0,0 +1,101 @@ + +Aplikacje pod klucz - wymagania klienta zamieniane w system webowy i aplikację brygady na telefon + + + + + + + + + + + + + + + + + + + + + + + + +WYMAGANIA KLIENTA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +SYSTEM WEBOWY + + + + + + + + + + + + + + + + + + + + +APLIKACJA BRYGADY + + + + + + + + + + + + + ANALIZA + PROJEKT + PROGRAMOWANIE + TESTY + WDROŻENIE + + diff --git a/assets/img/ekosystem.svg b/assets/img/ekosystem.svg new file mode 100644 index 0000000..a4ae0ce --- /dev/null +++ b/assets/img/ekosystem.svg @@ -0,0 +1,101 @@ + +Schemat ekosystemu SoftSPM - portal Wodociągowiec i marketplace Giełda Komunalna wymieniają tematy, praktyków i ogłoszenia z platformą kursową w centrum + + + + + + + + + + + + + + + + + + + + + + + + TEMATY Z RYNKU + PRAKTYCY + OGŁOSZENIA + + + + + + + WODOCIĄGOWIEC.PL + + + + + PORTAL BRANŻOWY · WIEDZA + + + + + + + GIEŁDAKOMUNALNA.PL + + + + + + + MARKETPLACE · WYSTAW ZA 0 ZŁ + + + + + + + PLATFORMA KURSOWA + + + + + + WEBINARIA · E-LEARNING · CERTYFIKATY + + + + + + + + + + diff --git a/assets/img/en/aplikacje.svg b/assets/img/en/aplikacje.svg new file mode 100644 index 0000000..a8b65b9 --- /dev/null +++ b/assets/img/en/aplikacje.svg @@ -0,0 +1,101 @@ + +Turnkey software - client requirements turned into a web system and a field crew app + + + + + + + + + + + + + + + + + + + + + + + + +CLIENT REQUIREMENTS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +WEB SYSTEM + + + + + + + + + + + + + + + + + + + + +FIELD CREW APP + + + + + + + + + + + + + ANALYSIS + DESIGN + DEVELOPMENT + TESTING + ROLLOUT + + diff --git a/assets/img/en/ekosystem.svg b/assets/img/en/ekosystem.svg new file mode 100644 index 0000000..e851956 --- /dev/null +++ b/assets/img/en/ekosystem.svg @@ -0,0 +1,101 @@ + +SoftSPM ecosystem diagram - the Wodociagowiec portal and the Gielda Komunalna marketplace exchange topics, practitioners and listings with the course platform at the centre + + + + + + + + + + + + + + + + + + + + + + + + MARKET TOPICS + EXPERTS + LISTINGS + + + + + + + WODOCIAGOWIEC.PL + + + + + TRADE PORTAL · KNOWLEDGE + + + + + + + GIELDAKOMUNALNA.PL + + + + + + + MARKETPLACE · FREE TO LIST + + + + + + + COURSE PLATFORM + + + + + + WEBINARS · E-LEARNING · CERTIFICATES + + + + + + + + + + diff --git a/assets/img/en/kurs-audyt.svg b/assets/img/en/kurs-audyt.svg new file mode 100644 index 0000000..05da3c7 --- /dev/null +++ b/assets/img/en/kurs-audyt.svg @@ -0,0 +1,54 @@ + +Security audit - review of system layers, a vulnerability list and NIS2 compliance + + + + + + + +TRACK · SECURITY AUDIT + + + + + + + + + +NIS2 COMPLIANCE + + + + + + + +VULNERABILITY LIST + + + + + + + + + + + + + + + + CRIT. + HIGH + +RECOMMENDATIONS BY PRIORITY + + diff --git a/assets/img/en/kurs-cyfryzacja.svg b/assets/img/en/kurs-cyfryzacja.svg new file mode 100644 index 0000000..e4082db --- /dev/null +++ b/assets/img/en/kurs-cyfryzacja.svg @@ -0,0 +1,78 @@ + +Digitising a utility - paper documentation turned into a system with a phased rollout plan + + + + + + + + + + +TRACK · DIGITISATION + + + + + + + + + + + + +PAPER AND SHEETS + + + + + + + + + + +CENTRAL SYSTEM + + + + + + + + + + + + + + + ASSETS + WORK ORDERS + READINGS + BILLING + REPORTING + + + + + + + + + + + + + PHASE I + PHASE II + PHASE III + + diff --git a/assets/img/en/kurs-gielda.svg b/assets/img/en/kurs-gielda.svg new file mode 100644 index 0000000..e00c3e1 --- /dev/null +++ b/assets/img/en/kurs-gielda.svg @@ -0,0 +1,68 @@ + +Gielda Komunalna - a free marketplace: warehouse surplus listed for manufacturers and contractors in the trade + + + + + + + + + + +ECOSYSTEM · GIELDA KOMUNALNA + + + + + + + + + + + + + +SITTING IN STORAGE + + + + + + + +LISTING + + + + + + +FREE +TO LIST + + + + + + + + + + + + + + + + +MAKERS · CONTRACTORS + diff --git a/assets/img/en/kurs-integracje.svg b/assets/img/en/kurs-integracje.svg new file mode 100644 index 0000000..a56e2a1 --- /dev/null +++ b/assets/img/en/kurs-integracje.svg @@ -0,0 +1,59 @@ + +Integration with ERP, CRM and tax systems - the data exchange bus + + + + + + + + + +TRACK · SYSTEM INTEGRATION + + + + + + + + + + ERP + CRM + TAX + BILLING + + + + + + + + + + + + +DATA EXCHANGE BUS + + + + + + + + + + + + REPORTING + WEB APPLICATIONS + + + diff --git a/assets/img/en/kurs-klient.svg b/assets/img/en/kurs-klient.svg new file mode 100644 index 0000000..b1d5fba --- /dev/null +++ b/assets/img/en/kurs-klient.svg @@ -0,0 +1,34 @@ + +Customer service and billing - customer request, invoice and communication + + + + + + + +TRACK · CUSTOMER SERVICE + + + + + +BILLING + + + + + + + + + + + + +REQUEST · INVOICE + + diff --git a/assets/img/en/kurs-portal.svg b/assets/img/en/kurs-portal.svg new file mode 100644 index 0000000..55c3a66 --- /dev/null +++ b/assets/img/en/kurs-portal.svg @@ -0,0 +1,35 @@ + +Wodociagowiec.pl - trade articles, operational cases and reader comments + + + + + + + +ECOSYSTEM · WODOCIAGOWIEC.PL + + + + + +TRADE ARTICLE + + + + + + + + + + + + + + + + + diff --git a/assets/img/en/kurs-prawo.svg b/assets/img/en/kurs-prawo.svg new file mode 100644 index 0000000..137968c --- /dev/null +++ b/assets/img/en/kurs-prawo.svg @@ -0,0 +1,31 @@ + +Water law and tariffs - tariff document, statutory symbol and approval stamp + + + + + + + +TRACK · LAW AND TARIFFS + + + + + + + + + + + + + + + + +TARIFF APPLICATION + + diff --git a/assets/img/en/kurs-scada.svg b/assets/img/en/kurs-scada.svg new file mode 100644 index 0000000..9595e22 --- /dev/null +++ b/assets/img/en/kurs-scada.svg @@ -0,0 +1,64 @@ + +Network monitoring and SCADA - control room desk with assets, a trend and an alarm + + + + + + + + + +TRACK · SCADA AND MONITORING + + + + + + + +NETWORK DESK + + + +ALARM + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + INTAKE + WTP + RESERVOIR + NETWORK + + + diff --git a/assets/img/en/kurs-zamowienia.svg b/assets/img/en/kurs-zamowienia.svg new file mode 100644 index 0000000..985d658 --- /dev/null +++ b/assets/img/en/kurs-zamowienia.svg @@ -0,0 +1,35 @@ + +Public procurement - comparing tenders against award criteria + + + + + + + +TRACK · PUBLIC PROCUREMENT + + + + + + + + + + + + TENDER A + TENDER B + TENDER C + + + + + + +CRITERIA + + diff --git a/assets/img/en/kurs-zarzadzanie.svg b/assets/img/en/kurs-zarzadzanie.svg new file mode 100644 index 0000000..47ce6f6 --- /dev/null +++ b/assets/img/en/kurs-zarzadzanie.svg @@ -0,0 +1,32 @@ + +Running a utility - financial indicators and cost structure + + + + + + + +TRACK · MANAGEMENT + + + +COST / REVENUE + + + + + + + + + + + + +COST STRUCTURE + + diff --git a/assets/img/en/platforma.svg b/assets/img/en/platforma.svg new file mode 100644 index 0000000..ad11946 --- /dev/null +++ b/assets/img/en/platforma.svg @@ -0,0 +1,81 @@ + +Student dashboard on the platform - course progress, upcoming webinars and a certificate + + + + + + + + + + + + + + + + + +STUDENT DASHBOARD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PROGRESS 65% + PROGRESS 30% + + + + + + + + +UPCOMING WEBINARS + + + + + + 15NOV + 22NOV + + + + + + + + + +LIVE + diff --git a/assets/img/en/szkolenia.svg b/assets/img/en/szkolenia.svg new file mode 100644 index 0000000..df86fe6 --- /dev/null +++ b/assets/img/en/szkolenia.svg @@ -0,0 +1,72 @@ + +Training room - instructor, a data screen and participants at workstations + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MAX. 12 + 2 × 8 H + + diff --git a/assets/img/konsulting.svg b/assets/img/konsulting.svg new file mode 100644 index 0000000..9829a14 --- /dev/null +++ b/assets/img/konsulting.svg @@ -0,0 +1,68 @@ + +Konsulting - analiza danych przedsiębiorstwa, raport i rekomendacje + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +WARIANTY · KOSZT I EFEKT + + + + + + + + + + + + + + + +LISTA DECYZJI + + + + + + + + + diff --git a/assets/img/kurs-audyt.svg b/assets/img/kurs-audyt.svg new file mode 100644 index 0000000..fe237a1 --- /dev/null +++ b/assets/img/kurs-audyt.svg @@ -0,0 +1,54 @@ + +Audyt bezpieczeństwa - przegląd warstw systemu, lista podatności i zgodność z NIS2 + + + + + + + +ŚCIEŻKA · AUDYT BEZPIECZEŃSTWA + + + + + + + + + +ZGODNOŚĆ NIS2 + + + + + + + +LISTA PODATNOŚCI + + + + + + + + + + + + + + + + KRYT. + WYS. + +REKOMENDACJE I PRIORYTETY + + diff --git a/assets/img/kurs-cyfryzacja.svg b/assets/img/kurs-cyfryzacja.svg new file mode 100644 index 0000000..022739c --- /dev/null +++ b/assets/img/kurs-cyfryzacja.svg @@ -0,0 +1,78 @@ + +Cyfryzacja przedsiębiorstwa - papierowa dokumentacja zamieniana w system z etapowym planem wdrożenia + + + + + + + + + + +ŚCIEŻKA · CYFRYZACJA + + + + + + + + + + + + +PAPIER I ARKUSZE + + + + + + + + + + +SYSTEM CENTRALNY + + + + + + + + + + + + + + + MAJĄTEK + ZLECENIA + ODCZYTY + BILLING + RAPORTOWANIE + + + + + + + + + + + + + ETAP I + ETAP II + ETAP III + + diff --git a/assets/img/kurs-gielda.svg b/assets/img/kurs-gielda.svg new file mode 100644 index 0000000..dc8f97f --- /dev/null +++ b/assets/img/kurs-gielda.svg @@ -0,0 +1,68 @@ + +Giełda Komunalna - bezpłatny marketplace: nadwyżki magazynowe trafiają jako ogłoszenia do producentów i wykonawców z branży + + + + + + + + + + +EKOSYSTEM · GIEŁDA KOMUNALNA + + + + + + + + + + + + + +ZALEGA W MAGAZYNIE + + + + + + + +OGŁOSZENIE + + + + + + +0 ZŁ +ZA WYSTAWIENIE + + + + + + + + + + + + + + + + +PRODUCENCI · WYKONAWCY + diff --git a/assets/img/kurs-integracje.svg b/assets/img/kurs-integracje.svg new file mode 100644 index 0000000..7c8d60c --- /dev/null +++ b/assets/img/kurs-integracje.svg @@ -0,0 +1,59 @@ + +Integracje z systemami ERP, CRM i podatkowymi - magistrala wymiany danych + + + + + + + + + +ŚCIEŻKA · INTEGRACJE SYSTEMÓW + + + + + + + + + + ERP + CRM + TAX + BILLING + + + + + + + + + + + + +MAGISTRALA DANYCH + + + + + + + + + + + + RAPORTOWANIE + APLIKACJE WWW + + + diff --git a/assets/img/kurs-klient.svg b/assets/img/kurs-klient.svg new file mode 100644 index 0000000..fdd114c --- /dev/null +++ b/assets/img/kurs-klient.svg @@ -0,0 +1,31 @@ + +Obsługa odbiorcy i billing - zgłoszenie, rozliczenie i komunikacja + + + + + + + +ŚCIEŻKA · OBSŁUGA KLIENTA + + + + + +ROZLICZENIE + + + + + + + + + + + + +ZGŁOSZENIE · FAKTURA + + diff --git a/assets/img/kurs-portal.svg b/assets/img/kurs-portal.svg new file mode 100644 index 0000000..1a83948 --- /dev/null +++ b/assets/img/kurs-portal.svg @@ -0,0 +1,33 @@ + +Wodociągowiec.pl - artykuły branżowe, przypadki z eksploatacji i komentarze + + + + + + + +EKOSYSTEM · WODOCIĄGOWIEC.PL + + + + + +ARTYKUŁ BRANŻOWY + + + + + + + + + + + + + + + + + diff --git a/assets/img/kurs-prawo.svg b/assets/img/kurs-prawo.svg new file mode 100644 index 0000000..5b5efd3 --- /dev/null +++ b/assets/img/kurs-prawo.svg @@ -0,0 +1,29 @@ + +Prawo i taryfy - dokument taryfowy, paragraf i pieczęć zatwierdzenia + + + + + + + +ŚCIEŻKA · PRAWO I TARYFY + + + + + + + + + + + + + + + + +WNIOSEK TARYFOWY + + diff --git a/assets/img/kurs-scada.svg b/assets/img/kurs-scada.svg new file mode 100644 index 0000000..46bcf0f --- /dev/null +++ b/assets/img/kurs-scada.svg @@ -0,0 +1,64 @@ + +Monitoring sieci i SCADA - pulpit dyspozytorski z obiektami, trendem i alarmem + + + + + + + + + +ŚCIEŻKA · SCADA I MONITORING + + + + + + + +PULPIT SIECI + + + +ALARM + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UJĘCIE + SUW + ZBIORNIK + SIEĆ + + + diff --git a/assets/img/kurs-zamowienia.svg b/assets/img/kurs-zamowienia.svg new file mode 100644 index 0000000..c1f10d6 --- /dev/null +++ b/assets/img/kurs-zamowienia.svg @@ -0,0 +1,31 @@ + +Zamówienia publiczne - porównanie ofert i kryteria oceny + + + + + + + +ŚCIEŻKA · ZAMÓWIENIA PUBLICZNE + + + + + + + + + + + + OFERTA AOFERTA BOFERTA C + + + + + + +KRYTERIA + + diff --git a/assets/img/kurs-zarzadzanie.svg b/assets/img/kurs-zarzadzanie.svg new file mode 100644 index 0000000..4dc9427 --- /dev/null +++ b/assets/img/kurs-zarzadzanie.svg @@ -0,0 +1,29 @@ + +Zarządzanie przedsiębiorstwem - wskaźniki finansowe i struktura kosztów + + + + + + + +ŚCIEŻKA · ZARZĄDZANIE + + + +KOSZT / PRZYCHÓD + + + + + + + + + + + + +STRUKTURA KOSZTÓW + + diff --git a/assets/img/platforma.svg b/assets/img/platforma.svg new file mode 100644 index 0000000..5a849a1 --- /dev/null +++ b/assets/img/platforma.svg @@ -0,0 +1,79 @@ + +Panel kursanta na platformie - postęp kursów, nadchodzące webinaria i certyfikat + + + + + + + + + + + + + + + + + +PANEL KURSANTA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + POSTĘP 65% + POSTĘP 30% + + + + + + + + +NADCHODZĄCE WEBINARIA + + + + + + 15LIS + 22LIS + + + + + + + + + +LIVE + diff --git a/assets/img/szkolenia.svg b/assets/img/szkolenia.svg new file mode 100644 index 0000000..92ce52a --- /dev/null +++ b/assets/img/szkolenia.svg @@ -0,0 +1,72 @@ + +Sala szkoleniowa - prowadzący, ekran z danymi i uczestnicy przy stanowiskach + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MAX. 12 + 2 × 8 H + + diff --git a/assets/img/webinaria.svg b/assets/img/webinaria.svg new file mode 100644 index 0000000..1efa7f0 --- /dev/null +++ b/assets/img/webinaria.svg @@ -0,0 +1,69 @@ + +Webinarium na żywo - okno prowadzącego, uczestnicy i panel czatu + + + + + + + + + + + + + + + + + + + + + +LIVE + + + + + + + + + + + + + + + + + + + + + + + + + +PYTANIA · 24 + + + + + + + + + + + + + + + + + ++ 118 UCZESTNIKÓW ONLINE + diff --git a/assets/styles.css b/assets/styles.css new file mode 100644 index 0000000..cb89b3d --- /dev/null +++ b/assets/styles.css @@ -0,0 +1,629 @@ +/* ============================================================================ + SoftSPM - system "Industry" / HydroNexis + Tokeny i komponenty wg doc/SoftSPM_projekt.doc v1.0. Jasny grunt techniczny, + jedna barwa akcentu (stalowa), rysunek liniowy, krzyzyki rejestracyjne. + Czysty CSS, bez build stepu. + ========================================================================= */ + +@import url("https://fonts.googleapis.com/css2?family=Barlow:wght@400;500;600&family=Barlow+Condensed:wght@500;600;700&display=swap"); + +:root { + --color-bg: #f2f2f3; + --color-surface: #e9e9ea; + --color-text: #1d1f20; + --color-accent: #5980a6; + --color-accent-2: #728fab; + --color-divider: color-mix(in srgb, #1d1f20 16%, transparent); + + /* Rampy tonalne - jedna wspolna skala jasnosci OKLCH */ + --color-neutral-100: #f5f5f8; + --color-neutral-200: #e7e7ea; + --color-neutral-300: #d4d4d7; + --color-neutral-400: #b7b7ba; + --color-neutral-500: #98989b; + --color-neutral-600: #7a7a7d; + --color-neutral-700: #5d5d60; + --color-neutral-800: #424244; + --color-neutral-900: #2b2b2d; + + --color-accent-100: #eef6ff; + --color-accent-200: #d6ebff; + --color-accent-300: #b5d9fd; + --color-accent-400: #94bce3; + --color-accent-500: #749dc4; + --color-accent-600: #597ea3; + --color-accent-700: #416180; + --color-accent-800: #2c455d; + --color-accent-900: #1d2d3d; + + --font-heading: "Barlow Condensed", system-ui, sans-serif; + --font-heading-weight: 600; + --font-body: "Barlow", system-ui, sans-serif; + + --space-1: 3.4px; + --space-2: 6.8px; + --space-3: 10.2px; + --space-4: 13.6px; + --space-6: 20.4px; + --space-8: 27.2px; + + --radius-sm: 2px; + --radius-md: 4px; + --radius-lg: 7px; + + --shadow-sm: 0 1px 2px color-mix(in srgb, #2b2b2d 14%, transparent); + --shadow-md: 0 3px 10px color-mix(in srgb, #2b2b2d 16%, transparent); + --shadow-lg: 0 12px 32px color-mix(in srgb, #2b2b2d 22%, transparent); + + /* Rytm strony - jednostka wiodaca 24px, polkrok 12px */ + --leading: 24px; + --half: 12px; + --edge: clamp(20px, 5vw, 72px); + --measure: 62ch; +} + +*, *::before, *::after { box-sizing: border-box; } + +html { scroll-behavior: smooth; } +@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } } + +body { + margin: 0; + background: var(--color-bg); + color: var(--color-text); + font-family: var(--font-body); + font-size: 16px; + line-height: 1.5; + text-wrap: pretty; + -webkit-font-smoothing: antialiased; +} +/* dlugie slowa i adresy nie rozpychaja ukladu na waskich ekranach */ +p, li, h1, h2, h3, h4, td, th, blockquote, figcaption { overflow-wrap: break-word; } + +h1, h2, h3, h4, h5, h6 { + font-family: var(--font-heading); + font-weight: var(--font-heading-weight); + line-height: 1.12; + letter-spacing: -0.015em; + margin: 0 0 var(--space-2); +} +h1 { font-size: 42px; } +h2 { font-size: 32px; } +h3 { font-size: 25px; } +h4 { font-size: 20px; } +h5 { font-size: 16px; } +h6 { font-size: 13px; letter-spacing: 0.08em; text-transform: uppercase; } + +p { margin: 0 0 var(--space-3); } +a { color: var(--color-accent-700); text-underline-offset: 3px; } +img { display: block; max-width: 100%; } + +.wrap { max-width: 1200px; margin: 0 auto; padding: 0 var(--edge); } +.text-muted { color: color-mix(in srgb, var(--color-text) 62%, transparent); } +.lead { font-size: 17.5px; line-height: var(--leading); max-width: var(--measure); } +.visually-hidden { + position: absolute; width: 1px; height: 1px; overflow: hidden; + clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; +} + +/* -- nawigacja ---------------------------------------------------------- */ +.nav { + position: sticky; top: 0; z-index: 50; + display: flex; align-items: center; gap: var(--space-4); + min-height: 62px; + padding-inline: max(var(--edge), calc((100% - 1200px) / 2 + var(--edge))); + background: color-mix(in srgb, var(--color-bg) 88%, transparent); + backdrop-filter: blur(10px); + border-bottom: 1px solid var(--color-divider); +} +.nav-brand { + display: inline-flex; align-items: center; + margin-right: var(--space-2); text-decoration: none; +} +.nav-brand img { height: 30px; width: auto; } +@media (max-width: 420px) { .nav-brand img { height: 26px; } } +.nav-links { display: flex; align-items: center; gap: var(--space-4); margin-left: auto; } +.nav a { + font-size: 14px; white-space: nowrap; text-decoration: none; + color: color-mix(in srgb, var(--color-text) 78%, transparent); +} +.nav a:hover { color: var(--color-accent-700); } +.nav a[aria-current] { color: var(--color-accent-700); font-weight: 500; } +.nav .btn { margin-left: var(--space-2); } +@media (max-width: 900px) { + .nav-links a:not(.nav-keep) { display: none; } +} + +/* -- linijki podpisowe: etykieta NAD kreska, tresc pod nia -------------- */ +.kicker { + display: block; font-size: 13px; line-height: var(--half); + letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600; + color: var(--color-accent-700); + font-feature-settings: "tnum" 1; + margin: 0 0 var(--half); +} +.caption-rule { + height: 1px; border: 0; background: var(--color-divider); + margin: 0 0 calc(var(--leading) - var(--half)); +} +.section-intro { max-width: var(--measure); margin-bottom: calc(1.5 * var(--leading)); } +.section-intro h2 { text-transform: uppercase; font-size: clamp(28px, 3.4vw, 40px); } + +/* -- krzyzyki rejestracyjne -------------------------------------------- */ +.corner { + position: absolute; width: 13px; height: 13px; + color: color-mix(in srgb, var(--color-text) 55%, transparent); + pointer-events: none; +} +.corner::before, .corner::after { content: ""; position: absolute; background: currentColor; } +.corner::before { left: 6px; top: 0; width: 1px; height: 100%; } +.corner::after { top: 6px; left: 0; width: 100%; height: 1px; } +.corner.tl { top: -7px; left: -7px; } +.corner.tr { top: -7px; right: -7px; } +.corner.bl { bottom: -7px; left: -7px; } +.corner.br { bottom: -7px; right: -7px; } + +/* -- przyciski ---------------------------------------------------------- */ +.btn { + display: inline-flex; align-items: center; justify-content: center; gap: 6px; + cursor: pointer; text-decoration: none; + font-family: var(--font-heading); font-weight: var(--font-heading-weight); + font-size: 15px; line-height: 1.2; color: var(--color-text); + background: transparent; + border: 1px solid var(--color-divider); border-radius: 0; + padding: 9px 16px; +} +.btn svg { display: block; } +.btn-primary { + background: var(--color-accent); color: var(--color-bg); + border-color: var(--color-accent); +} +.btn-primary:hover { background: var(--color-accent-600); border-color: var(--color-accent-600); } +.btn-primary:active { background: var(--color-accent-700); border-color: var(--color-accent-700); } +.btn-ghost { color: var(--color-accent-700); border-color: var(--color-divider); } +.btn-ghost:hover { background: color-mix(in srgb, var(--color-accent) 10%, transparent); } +.btn-ghost:active { background: color-mix(in srgb, var(--color-accent) 18%, transparent); } +.btn-lg { padding: 12px 22px; font-size: 17px; } +.btn-block { width: 100%; } +.btn:focus-visible { outline: 2px solid var(--color-accent-700); outline-offset: 2px; } + +/* -- tagi --------------------------------------------------------------- */ +.tag { + display: inline-flex; align-items: center; gap: 5px; + padding: 2px 8px; border-radius: 0; + font-size: 11px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; + border: 1px solid var(--color-divider); white-space: nowrap; +} +.tag-accent { background: var(--color-accent); color: var(--color-bg); border-color: var(--color-accent); } +.tag-outline { color: var(--color-accent-700); border-color: var(--color-accent-400); } +.tag-neutral { color: color-mix(in srgb, var(--color-text) 65%, transparent); } +.tag-live { background: var(--color-accent-900); color: var(--color-bg); border-color: var(--color-accent-900); } +/* status zamiast daty - termin jeszcze nieustalony */ +.tag-soon { + color: var(--color-accent-700); + border-style: dashed; border-color: var(--color-accent-400); + letter-spacing: 0.08em; +} + +/* -- formularze --------------------------------------------------------- */ +.field { margin-bottom: var(--space-3); } +.field > label { + display: block; font-size: 12px; margin-bottom: 5px; + letter-spacing: 0.04em; text-transform: uppercase; font-weight: 600; + color: color-mix(in srgb, var(--color-text) 70%, transparent); +} +.input { + width: 100%; min-height: 40px; padding: 8px 11px; + font: inherit; font-size: 15px; + color: var(--color-text); caret-color: var(--color-accent); + background: color-mix(in srgb, var(--color-bg) 60%, #fff); + border: 1px solid var(--color-divider); border-radius: 0; +} +.input:hover { border-color: color-mix(in srgb, var(--color-text) 45%, transparent); } +.input:focus-visible { border-color: var(--color-accent); outline: 1px solid var(--color-accent); outline-offset: 0; } +textarea.input { min-height: 110px; resize: vertical; } +.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 var(--space-4); } +@media (max-width: 640px) { .form-grid { grid-template-columns: 1fr; } } +.form-note { font-size: 12.5px; color: color-mix(in srgb, var(--color-text) 55%, transparent); } + +/* -- ramka blueprint / plyta arkusza ----------------------------------- */ +.plate { position: relative; border: 1px solid var(--color-divider); background: transparent; } + +.title-block { + display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-4); + padding: var(--half) var(--space-4); + border-bottom: 1px solid var(--color-divider); + background: color-mix(in srgb, var(--color-accent) 7%, transparent); +} +.tb-title { + font-family: var(--font-heading); font-weight: 600; + font-size: 16px; letter-spacing: 0.04em; text-transform: uppercase; + margin-right: auto; +} +.tb-cell { + font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; + font-feature-settings: "tnum" 1; + color: color-mix(in srgb, var(--color-text) 62%, transparent); +} + +/* -- tabele ------------------------------------------------------------- */ +.table { width: 100%; border-collapse: collapse; font-size: 15px; } +.table th { + text-align: left; font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; + font-weight: 600; + color: color-mix(in srgb, var(--color-text) 60%, transparent); + padding: var(--space-3) var(--space-4); + border-bottom: 1px solid var(--color-divider); +} +.table td { + padding: var(--space-3) var(--space-4); + border-bottom: 1px solid color-mix(in srgb, var(--color-text) 8%, transparent); + vertical-align: middle; +} +.table tbody tr:last-child td { border-bottom: 0; } +.table tbody tr:hover { background: color-mix(in srgb, var(--color-text) 4%, transparent); } + +.s-num, .s-val, .s-date { font-feature-settings: "tnum" 1; } +.s-num { color: color-mix(in srgb, var(--color-text) 55%, transparent); width: 3rem; } +.s-val { + font-family: var(--font-heading); font-weight: 700; font-size: 21px; + color: var(--color-accent-800); white-space: nowrap; +} +.s-prop { font-weight: 500; } +.s-rem { font-size: 14px; color: color-mix(in srgb, var(--color-text) 68%, transparent); } +.s-date { white-space: nowrap; font-weight: 500; width: 1%; } +.s-topic { font-family: var(--font-heading); font-weight: 600; font-size: 18px; } +.s-act { text-align: right; white-space: nowrap; } +.sign { + font-family: var(--font-heading); font-weight: 600; font-size: 15px; + color: var(--color-accent-700); text-decoration: none; +} +.sign:hover { text-decoration: underline; } + +.sheet-note { + margin: 0; padding: var(--half) var(--space-4); + border-top: 1px solid var(--color-divider); + font-size: 12px; letter-spacing: 0.03em; + color: color-mix(in srgb, var(--color-text) 55%, transparent); +} + +/* -- zdjecia: duotone w ramce blueprint -------------------------------- */ +.duotone { position: relative; overflow: hidden; background: var(--color-accent-900); } +.duotone img { width: 100%; height: 100%; object-fit: cover; filter: grayscale(1) contrast(1.08) brightness(1.04); } +.duotone::after { + content: ""; position: absolute; inset: 0; pointer-events: none; + background: var(--color-accent); mix-blend-mode: screen; opacity: 0.62; +} +.figure { position: relative; border: 1px solid var(--color-divider); } +.figure .duotone { aspect-ratio: 4 / 3; } +/* rysunki wlasne (SVG) - juz sa w kolorystyce systemu, bez duotone */ +.plot { display: block; width: 100%; background: #eaeef2; } +.figure .plot { border-bottom: 1px solid var(--color-divider); } +.figure figcaption { + padding: var(--half) var(--space-4); + border-top: 1px solid var(--color-divider); + font-size: 12px; letter-spacing: 0.05em; text-transform: uppercase; + color: color-mix(in srgb, var(--color-text) 58%, transparent); +} + +/* -- ikony -------------------------------------------------------------- */ +.ico { width: 28px; height: 28px; stroke: var(--color-accent); stroke-width: 1.5; fill: none; + stroke-linecap: round; stroke-linejoin: round; } +.ico-sm { width: 16px; height: 16px; stroke-width: 1.5; } + +/* -- sekcje ------------------------------------------------------------- */ +.section { padding: calc(3 * var(--leading)) 0; } +.section-tight { padding: calc(2 * var(--leading)) 0; } + +/* -- hero --------------------------------------------------------------- */ +.hero { padding: calc(3.5 * var(--leading)) 0 calc(2.5 * var(--leading)); } +.hero-grid { display: grid; grid-template-columns: 1.25fr 1fr; gap: calc(2 * var(--leading)); align-items: center; } +.display { + font-family: var(--font-heading); font-weight: var(--font-heading-weight); + /* skalowanie wzgledem szerokosci kolumny hero, zeby dluzsza linia + ("W PRAKTYCE OD 2014.") miescila sie bez lamania w srodku slowa */ + font-size: clamp(40px, 5.1cqi, 82px); + line-height: 1.04; + letter-spacing: 0.01em; text-transform: uppercase; margin: 0; + margin-left: -0.052em; + text-wrap: balance; +} +.display .line { display: block; } +.hero-grid > div:first-child { container-type: inline-size; } +@supports not (font-size: 1cqi) { + .display { font-size: clamp(40px, 5.6vw, 82px); } +} +.hero .sub { + font-size: 17px; line-height: var(--leading); max-width: var(--measure); + margin: var(--leading) 0 0; +} +.hero .row { display: flex; gap: var(--space-4); flex-wrap: wrap; margin-top: calc(1.5 * var(--leading)); } +.hero-marks { + display: flex; flex-wrap: wrap; gap: var(--space-4) var(--space-8); + margin-top: calc(1.5 * var(--leading)); padding-top: var(--half); + border-top: 1px solid var(--color-divider); + font-size: 13px; letter-spacing: 0.05em; text-transform: uppercase; + color: color-mix(in srgb, var(--color-text) 58%, transparent); +} +.page-hero { padding: calc(2.5 * var(--leading)) 0 calc(1.5 * var(--leading)); } +.page-hero h1 { font-size: clamp(40px, 5.4vw, 68px); text-transform: uppercase; margin-left: -0.052em; } +@media (max-width: 880px) { .hero-grid { grid-template-columns: 1fr; } .hero-figure { display: none; } } + +/* -- siatka komorek (karty jako rysunki liniowe) ----------------------- */ +.cells { display: grid; gap: calc(1.5 * var(--leading)); } +.cells-2 { grid-template-columns: repeat(2, 1fr); } +.cells-3 { grid-template-columns: repeat(3, 1fr); } +.cells-4 { grid-template-columns: repeat(4, 1fr); } +@media (max-width: 980px) { .cells-3, .cells-4 { grid-template-columns: repeat(2, 1fr); } } +@media (max-width: 640px) { .cells-2, .cells-3, .cells-4 { grid-template-columns: 1fr; } } + +.cell-frame { + position: relative; display: flex; flex-direction: column; + padding: var(--space-6); + border: 1px solid var(--color-divider); background: transparent; +} +.cell-frame h2, .cell-frame h3 { font-size: 25px; margin-bottom: var(--half); } +.cell-frame p { font-size: 15px; flex: 1; margin-bottom: var(--space-4); } +.cell-frame .ico { margin-bottom: var(--space-3); } +.cell-link { + font-family: var(--font-heading); font-weight: 600; font-size: 15px; + color: var(--color-accent-700); text-decoration: none; margin-top: auto; +} +.cell-link:hover { text-decoration: underline; } +.cell-span { grid-column: 1 / -1; } +.cell-num { + position: absolute; top: var(--space-3); right: var(--space-4); + font-size: 12px; letter-spacing: 0.08em; + font-feature-settings: "tnum" 1; + color: color-mix(in srgb, var(--color-text) 35%, transparent); +} + +/* -- karta kursu -------------------------------------------------------- */ +.course { + position: relative; display: flex; flex-direction: column; + border: 1px solid var(--color-divider); +} +.course .duotone { aspect-ratio: 16 / 9; border-bottom: 1px solid var(--color-divider); } +.course .plot { width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; border-bottom: 1px solid var(--color-divider); } +.expert .plot { width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; border-bottom: 1px solid var(--color-divider); } +.course-body { padding: var(--space-6); display: flex; flex-direction: column; flex: 1; } +.course-tags { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-3); } +.course h3 { font-size: 22px; margin-bottom: var(--half); } +.course p { font-size: 14.5px; flex: 1; } +.course-meta { + display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); + padding-top: var(--half); margin-bottom: var(--space-4); + border-top: 1px solid var(--color-divider); + font-size: 13px; color: color-mix(in srgb, var(--color-text) 62%, transparent); +} +.course-meta span { display: inline-flex; align-items: center; gap: 5px; } +.course-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); } +.price { + font-family: var(--font-heading); font-weight: 700; font-size: 24px; + color: var(--color-accent-800); font-feature-settings: "tnum" 1; line-height: 1.1; +} +.price small { font-size: 11.5px; font-weight: 500; letter-spacing: 0.06em; + color: color-mix(in srgb, var(--color-text) 55%, transparent); display: block; } + +/* -- uklad dwudzielny --------------------------------------------------- */ +.split { display: grid; grid-template-columns: 1fr 1fr; gap: calc(2 * var(--leading)); align-items: center; } +@media (max-width: 880px) { .split { grid-template-columns: 1fr; } .split-media { order: 2; } } +.split-title { font-size: clamp(30px, 3.6vw, 42px); text-transform: uppercase; margin-bottom: var(--space-4); } + +/* -- lista kontrolna ---------------------------------------------------- */ +.checks { list-style: none; margin: 0 0 var(--space-6); padding: 0; } +.checks li { + position: relative; padding-left: 26px; margin-bottom: var(--half); + font-size: 15.5px; +} +.checks li::before { + content: ""; position: absolute; left: 0; top: 7px; + width: 11px; height: 6px; + border-left: 1.5px solid var(--color-accent); border-bottom: 1.5px solid var(--color-accent); + transform: rotate(-45deg); +} + +/* -- kroki procesu ------------------------------------------------------ */ +.steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: calc(1.5 * var(--leading)); } +@media (max-width: 780px) { .steps { grid-template-columns: 1fr; } } +.step { position: relative; padding-top: var(--space-6); border-top: 2px solid var(--color-accent); } +.step-num { + font-family: var(--font-heading); font-weight: 700; font-size: 44px; line-height: 1; + color: color-mix(in srgb, var(--color-accent) 42%, transparent); + font-feature-settings: "tnum" 1; display: block; margin-bottom: var(--space-3); +} +.step h3 { font-size: 22px; } +.step p { font-size: 15px; margin-bottom: 0; } + +/* -- eksperci ----------------------------------------------------------- */ +.expert { position: relative; border: 1px solid var(--color-divider); display: flex; flex-direction: column; } +.expert .duotone { aspect-ratio: 1 / 1; border-bottom: 1px solid var(--color-divider); } +.expert-body { padding: var(--space-6); } +.expert h3 { font-size: 21px; margin-bottom: 2px; } +.expert .role { + font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase; + color: var(--color-accent-700); margin-bottom: var(--space-3); +} +.expert p { font-size: 14.5px; margin-bottom: 0; } + +/* -- cytat -------------------------------------------------------------- */ +.quote { padding: calc(2.5 * var(--leading)) 0; } +.quote blockquote { + margin: 0; font-family: var(--font-heading); font-weight: 600; + font-size: clamp(24px, 3.1vw, 36px); line-height: 1.24; + max-width: 28ch; margin-left: -0.316em; +} +.quote figcaption { + margin-top: var(--leading); font-size: 14px; + color: color-mix(in srgb, var(--color-text) 62%, transparent); + margin-left: -0.885em; +} + +/* -- pasek ekosystemu --------------------------------------------------- */ +.eco { display: grid; grid-template-columns: repeat(3, 1fr); gap: calc(1.5 * var(--leading)); } +@media (max-width: 780px) { .eco { grid-template-columns: 1fr; } } +.eco-item { + position: relative; padding: var(--space-6); + border: 1px solid var(--color-divider); + text-decoration: none; color: inherit; display: block; +} +.eco-item:hover { background: color-mix(in srgb, var(--color-accent) 6%, transparent); } +.eco-item h3 { font-size: 21px; margin-bottom: var(--space-2); } +.eco-item p { font-size: 14.5px; margin-bottom: var(--space-2); } +.eco-url { + font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase; + color: var(--color-accent-700); +} + +/* -- newsletter / CTA --------------------------------------------------- */ +.cta-band { + position: relative; padding: calc(2 * var(--leading)) var(--space-8); + border: 1px solid var(--color-divider); + background: color-mix(in srgb, var(--color-accent) 7%, transparent); +} +.form-row { display: flex; gap: var(--space-3); flex-wrap: wrap; max-width: 560px; } +.form-row .input { flex: 1 1 240px; } + +/* -- zgoda na kontakt --------------------------------------------------- */ +.consent { + display: grid; grid-template-columns: auto 1fr; gap: var(--space-3); + align-items: start; max-width: var(--measure); + margin: var(--leading) 0 var(--space-4); + font-size: 13.5px; line-height: calc(var(--leading) - 3px); + color: color-mix(in srgb, var(--color-text) 78%, transparent); + cursor: pointer; +} +.consent input[type="checkbox"] { + appearance: none; -webkit-appearance: none; + width: 18px; height: 18px; margin: 2px 0 0; flex: none; + border: 1px solid color-mix(in srgb, var(--color-text) 38%, transparent); + background: var(--color-bg); cursor: pointer; + display: grid; place-content: center; +} +.consent input[type="checkbox"]::before { + content: ""; width: 10px; height: 10px; transform: scale(0); + background: var(--color-bg); + clip-path: polygon(14% 46%, 0 60%, 40% 100%, 100% 20%, 86% 8%, 38% 72%); +} +.consent input[type="checkbox"]:checked { + background: var(--color-accent); border-color: var(--color-accent); +} +.consent input[type="checkbox"]:checked::before { transform: scale(1); } +.consent input[type="checkbox"]:focus-visible { outline: 2px solid var(--color-accent-700); outline-offset: 2px; } +.consent .req { color: var(--color-accent-700); font-weight: 500; } + +/* -- suwak antybotowy --------------------------------------------------- */ +.verify { max-width: 420px; margin: var(--leading) 0; } +.verify-label { + display: block; margin-bottom: var(--space-2); + font-family: var(--font-heading); font-weight: 600; + font-size: 13px; letter-spacing: 0.08em; text-transform: uppercase; + color: color-mix(in srgb, var(--color-text) 62%, transparent); +} +.verify-track { + position: relative; height: 46px; overflow: hidden; + border: 1px solid color-mix(in srgb, var(--color-text) 30%, transparent); + background: var(--color-bg); touch-action: none; user-select: none; +} +.verify-fill { + position: absolute; inset: 0 auto 0 0; width: 0; + background: color-mix(in srgb, var(--color-accent) 16%, transparent); + pointer-events: none; +} +.verify-hint { + position: absolute; inset: 0; display: grid; place-content: center; + padding-left: 44px; pointer-events: none; + font-size: 14px; color: color-mix(in srgb, var(--color-text) 55%, transparent); +} +.verify-handle { + position: absolute; top: -1px; left: 0; width: 60px; height: 46px; + display: grid; place-content: center; cursor: grab; + border: 1px solid var(--color-accent); background: var(--color-accent); + color: var(--color-bg); transition: left .18s ease; +} +.verify-handle svg { width: 20px; height: 20px; stroke: currentColor; stroke-width: 1.8; fill: none; } +.verify-handle:focus-visible { outline: 2px solid var(--color-accent-700); outline-offset: 2px; } +.verify.is-dragging .verify-handle { cursor: grabbing; transition: none; } +.verify.is-verified .verify-track { border-color: var(--color-accent); } +.verify.is-verified .verify-handle { background: var(--color-accent-700); border-color: var(--color-accent-700); cursor: default; } +.verify.is-verified .verify-hint { color: var(--color-accent-700); font-weight: 500; } +.verify-check { display: none; } +.verify.is-verified .verify-chevrons { display: none; } +.verify.is-verified .verify-check { display: block; } +@media (prefers-reduced-motion: reduce) { .verify-handle { transition: none; } } + +/* -- pole-pułapka: widoczne wyłącznie dla automatów --------------------- */ +.hp { + position: absolute; left: -9999px; top: auto; + width: 1px; height: 1px; overflow: hidden; +} + +/* -- komunikat po wysłaniu formularza ----------------------------------- */ +.form-status { + margin: var(--space-3) 0 0; min-height: var(--leading); + font-size: 14px; line-height: var(--leading); +} +.form-status:empty { margin: 0; min-height: 0; } +.form-status.is-ok { color: var(--color-accent-700); font-weight: 500; padding-left: var(--space-3); border-left: 2px solid var(--color-accent); } +/* System dopuszcza jedną barwę akcentu - błąd odróżniamy tonem i wypełnieniem, + nie nowym kolorem. Sam komunikat i tak mówi wprost, co się stało. */ +.form-status.is-error { + color: var(--color-accent-900); font-weight: 500; + padding: var(--space-2) var(--space-3); + border-left: 2px solid var(--color-accent-900); + background: color-mix(in srgb, var(--color-accent) 10%, transparent); +} +.form-status.is-busy { color: color-mix(in srgb, var(--color-text) 60%, transparent); } + +/* -- stopka ------------------------------------------------------------- */ +.foot { + margin-top: calc(2 * var(--leading)); + border-top: 1px solid var(--color-divider); + padding: calc(2 * var(--leading)) 0 calc(1.5 * var(--leading)); + font-size: 14px; +} +.foot-grid { display: grid; grid-template-columns: 1.4fr 1fr 1fr 1fr; gap: calc(1.5 * var(--leading)); } +@media (max-width: 880px) { .foot-grid { grid-template-columns: 1fr 1fr; } } +@media (max-width: 520px) { .foot-grid { grid-template-columns: 1fr; } } +.foot-logo { height: 42px; width: auto; margin-bottom: var(--space-4); } +.foot h4 { font-size: 13px; letter-spacing: 0.08em; text-transform: uppercase; + color: color-mix(in srgb, var(--color-text) 58%, transparent); margin-bottom: var(--space-3); } +.foot ul { list-style: none; margin: 0; padding: 0; } +.foot li { margin-bottom: var(--space-2); } +.foot a { text-decoration: none; color: color-mix(in srgb, var(--color-text) 80%, transparent); } +.foot a:hover { color: var(--color-accent-700); text-decoration: underline; } +.foot-bar { + margin-top: calc(1.5 * var(--leading)); padding-top: var(--half); + border-top: 1px solid var(--color-divider); + display: flex; flex-wrap: wrap; gap: var(--space-4); justify-content: space-between; + font-size: 12.5px; color: color-mix(in srgb, var(--color-text) 55%, transparent); +} + +/* -- responsywne tabele: wiersze skladaja sie do kart ------------------- */ +@media (max-width: 760px) { + .table.sched thead { display: none; } + .table.sched tbody tr { + display: grid; grid-template-columns: 1fr auto; gap: 4px var(--space-3); + padding: var(--space-4); border-bottom: 1px solid var(--color-divider); + } + .table.sched td { border: 0; padding: 0; } + .table.sched .s-date { order: 1; font-size: 14px; color: var(--color-accent-700); } + .table.sched .s-form { order: 2; text-align: right; } + .table.sched .s-topic { grid-column: 1 / -1; order: 3; } + .table.sched .s-place { order: 4; font-size: 13.5px; color: color-mix(in srgb, var(--color-text) 60%, transparent); } + .table.sched .s-act { order: 5; text-align: right; } + + .table.spec thead { display: none; } + .table.spec tbody tr { + display: grid; grid-template-columns: auto 1fr; gap: 2px var(--space-3); + padding: var(--space-4); border-bottom: 1px solid var(--color-divider); + } + .table.spec td { border: 0; padding: 0; } + .table.spec .s-val { grid-column: 2; } + .table.spec .s-rem { grid-column: 2; } +} + +@media print { + .nav, .btn { display: none; } + body { background: #fff; } +} diff --git a/doc/SoftSPM_makieta.html b/doc/SoftSPM_makieta.html new file mode 100644 index 0000000..210a555 --- /dev/null +++ b/doc/SoftSPM_makieta.html @@ -0,0 +1,385 @@ + + + + + SoftSPM — szkolenia, konsulting i webinaria dla branży wod-kan + +

+ This page requires JavaScript to display. +
+ + + +
SPM
+
Unpacking...
+ + + + + + + + + + + + \ No newline at end of file diff --git a/doc/SoftSPM_projekt.doc b/doc/SoftSPM_projekt.doc new file mode 100644 index 0000000..4ec62c6 --- /dev/null +++ b/doc/SoftSPM_projekt.doc @@ -0,0 +1,93 @@ + +SoftSPM — projekt nowej strony + + + +

SoftSPM — projekt nowej strony internetowej

+

Dokumentacja projektowa · wersja 1.0 · sierpień 2026 · makieta: SoftSPM_makieta.html

+ +

1. Cel i założenia

+

Nowa strona SoftSPM zachowuje dotychczasowy charakter i zakres firmy (system sterowania pompami głębinowymi), ale przenosi ciężar komunikacji na nowe produkty: szkolenia, konsulting i webinaria dla branży wodociągowo-kanalizacyjnej. Główny przekaz: wiedza i doświadczenie praktyków od 2014 roku — programy budowane na rzeczywistych wdrożeniach, nie na slajdach.

+ + +

2. Struktura serwisu

+ + + + + + +
StronaPlikZawartość
Strona główna (PL)index.htmlHero · Doświadczenie w liczbach (arkusz techniczny) · Oferta (szkolenia / konsulting / webinaria) · Najbliższe terminy · Platforma kursowa · System SoftSPM · Opinia · Newsletter · Stopka z ekosystemem
Szkolenia / Webinariaszkolenia.htmlHero · 5 ścieżek tematycznych · Format szkolenia (arkusz) · Terminy · Webinaria + pakiet roczny · Zgłoszenie
Konsultingkonsulting.htmlHero · Zakres usług (audyt, dobór pomp, SCADA, cyfryzacja) · Przebieg współpracy w 3 krokach · Opinia · Bezpłatna rozmowa wstępna
Strona główna (EN)index-en.htmlAngielskie lustro strony głównej
+ +

3. Ścieżki tematyczne szkoleń

+ + +

4. Design — system „Industry" (HydroNexis)

+

Strona konsekwentnie stosuje styl wireframe/blueprint: jasne techniczne tło, stalowo-niebieski akcent, kwadratowe narożniki z krzyżykami rejestracyjnymi („+"), karty jako rysunki liniowe bez wypełnienia, zdjęcia w duotonie akcentu. Liczby i parametry prezentowane jako arkusze techniczne (tabela spec z nagłówkiem rewizji).

+ +

4.1. Kolorystyka

+ + + + + + +
RolaWartośćZastosowanie
Tło#f2f2f3Jasny techniczny grunt całej strony
Tekst#1d1f20Treść, nagłówki
Akcent (stalowy)#5980a6Przyciski primary, tagi, linki, duotone zdjęć
Akcent ciemny (700–900)ramp OKLCHTekst akcentowy w rozmiarze akapitu, stany pressed
+

Zasada: jedna barwa akcentu, bez dodatkowych kolorów dekoracyjnych. Tinty i stany hover z tonalnej skali 100–900.

+ +

4.2. Typografia

+ + + + +
RolaKrójCharakter
NagłówkiBarlow CondensedSkondensowane, wersaliki w tytułach sekcji i tabel
TekstBarlowCzytelny grotesk, interlinia rytmu 24px
+ +

4.3. Komponenty

+ + +

5. Treści do potwierdzenia przed publikacją

+ + +

6. Następne kroki

+ + + \ No newline at end of file diff --git a/doc/manual_send_email.html b/doc/manual_send_email.html new file mode 100644 index 0000000..7ac4b9e --- /dev/null +++ b/doc/manual_send_email.html @@ -0,0 +1,227 @@ + + + + + + Manual API - send_email_api.php + + + +
+
+

Manual programisty: send_email_api.php

+

Dokumentacja endpointu do wysyłki e-mail przez SMTP (PHPMailer).

+ POST + application/json + Auth: Bearer token w Authorization +
+ +
+

1. Endpoint

+ + + + + + +
Adreshttps://api.softspm.pl/send_email_api.php
Metoda HTTPPOST
PreflightOPTIONS zwraca 204
Content-Typeapplication/json
KodowanieUTF-8
+
+ +
+

2. Autoryzacja

+

Klucz API należy przekazać w nagłówku Authorization jako Bearer token:

+
Authorization: Bearer TWÓJ_KLUCZ_API
+

Uwaga: token musi znajdować się na liście kluczy API_KEYS['send_email_api.php'] po stronie serwera.

+

Brak nagłówka lub błędny token zwraca HTTP 401.

+
+ +
+

3. Parametry wejściowe (JSON body)

+ + + + + + + +
ParametrTypWymaganyOpis
tostringtakAdres e-mail odbiorcy. Musi przejść walidację FILTER_VALIDATE_EMAIL.
subject lub title lub tytulstringtakTytuł wiadomości. Max 255 znaków.
message lub body lub content lub trescstringtakTreść wiadomości. Max 20000 znaków.
+ +

Przykładowe body

+
{
+  "to": "jan.kowalski@example.com",
+    "title": "Przypomnienie",
+    "body": "Wydarzenie odbędzie się jutro o 10:00."
+}
+
+ +
+

4. Przykład request (cURL)

+
curl -X POST "https://api.softspm.pl/send_email_api.php" \
+  -H "Content-Type: application/json" \
+  -H "Authorization: Bearer TWÓJ_KLUCZ_API" \
+  -d '{
+    "to":"jan.kowalski@example.com",
+        "tytul":"Test API mail",
+        "tresc":"To jest test wiadomości e-mail"
+  }'
+
+ +
+

5. Przykład request (JavaScript / fetch)

+
const response = await fetch("https://api.softspm.pl/send_email_api.php", {
+  method: "POST",
+  headers: {
+    "Content-Type": "application/json",
+    "Authorization": "Bearer TWÓJ_KLUCZ_API"
+  },
+  body: JSON.stringify({
+    to: "jan.kowalski@example.com",
+        title: "Powiadomienie",
+        content: "Treść wiadomości"
+  })
+});
+
+const data = await response.json();
+console.log(response.status, data);
+
+ +
+

6. Odpowiedzi API

+ +

6.1 Sukces (HTTP 200)

+
{
+  "success": "Message has been sent"
+}
+ +

6.2 Błędy

+ + + + + + + + + + + + + + +
HTTPKomunikatZnaczenie
400Empty JSON payload.Puste body.
400Invalid JSON payload.Niepoprawna składnia JSON.
400Missing required fields: to, subject/title/tytul, message/body/content/trescBrak wymaganych pól.
400Invalid recipient e-mail address.Niepoprawny adres e-mail odbiorcy.
400Subject is too long.Przekroczono limit 255 znaków.
400Message is too long.Przekroczono limit 20000 znaków.
401Unauthorized - Invalid or missing API key.Brak lub błędny Bearer token.
405Only POST requests are allowed.Użyto innej metody HTTP niż POST.
500Mailer Error: message could not be sent.Błąd SMTP/PHPMailer po stronie serwera.
204(pusta odpowiedź)Poprawny preflight OPTIONS.
+
+ +
+

7. Uwagi implementacyjne

+
    +
  • Endpoint ustawia nagłówki CORS: Access-Control-Allow-Origin: *, Allow-Methods: POST, OPTIONS, Allow-Headers: Content-Type, Authorization.
  • +
  • Wiadomość jest wysyłana jako HTML oraz alternatywnie jako plain text (AltBody).
  • +
  • Tytuł i treść są sanityzowane przed osadzeniem w szablonie HTML.
  • +
  • Szczegóły błędów SMTP są logowane po stronie serwera (nie są zwracane klientowi).
  • +
+
+ +
+

8. Checklista testów integracyjnych

+
    +
  • Poprawny request z Bearer tokenem zwraca HTTP 200.
  • +
  • Brak Authorization zwraca HTTP 401.
  • +
  • Błędny token zwraca HTTP 401.
  • +
  • Brak pola to, tytułu (subject/title/tytul) lub treści (message/body/content/tresc) zwraca HTTP 400.
  • +
  • Niepoprawny e-mail odbiorcy zwraca HTTP 400.
  • +
  • Za długie subject/message zwracają HTTP 400.
  • +
  • Żądanie metodą GET zwraca HTTP 405.
  • +
  • Żądanie OPTIONS zwraca HTTP 204.
  • +
+
+
+ + diff --git a/doc/manual_send_sms.html b/doc/manual_send_sms.html new file mode 100644 index 0000000..6fdd8b5 --- /dev/null +++ b/doc/manual_send_sms.html @@ -0,0 +1,223 @@ + + + + + + Manual API - send_sms_api.php + + + +
+
+

Manual programisty: send_sms_api.php

+

Dokumentacja endpointu do dodawania wiadomości SMS do kolejki wysyłkowej (tabela outbox).

+ POST + application/json + Auth: api_key w body JSON +
+ +
+

1. Endpoint

+ + + + + +
Adreshttps://api.softspm.pl/send_sms_api.php
Metoda HTTPPOST
Content-Typeapplication/json
KodowanieUTF-8
+
+ +
+

2. Autoryzacja

+

Klucz API należy wysłać w polu api_key w treści JSON.

+

Uwaga: klucz musi znajdować się na liście API_KEYS['send_sms_api.php'] po stronie serwera.

+
{
+  "api_key": "TWÓJ_KLUCZ_API",
+    "to": "+48XXXYYYZZZ",
+  "message": "Treść SMS",
+  "creator": "CRM"
+}
+
+ +
+

3. Parametry wejściowe

+ + + + + + + + + + +
ParametrTypWymaganyOpis
api_keystringtakKlucz API.
tostringtakNumer odbiorcy. Dozwolone znaki: cyfry, +, spacje, myślniki, nawiasy. Max 30 znaków.
messagestringtakTreść SMS. Max 1530 znaków.
creatorstringnieŹródło wiadomości. Domyślnie API. Max 50 znaków.
+
+ +
+

4. Przykład request (cURL)

+
curl -X POST "https://api.softspm.pl/send_sms_api.php" \
+  -H "Content-Type: application/json" \
+  -d '{
+    "api_key":"TWÓJ_KLUCZ_API",
+        "to":"+48XXXYYYZZZ",
+    "message":"Przypomnienie o wydarzeniu jutro o 10:00",
+    "creator":"HydroNexis"
+  }'
+
+ +
+

5. Przykład request (JavaScript / fetch)

+
const response = await fetch("https://api.softspm.pl/send_sms_api.php", {
+  method: "POST",
+  headers: { "Content-Type": "application/json" },
+  body: JSON.stringify({
+    api_key: "TWÓJ_KLUCZ_API",
+        to: "+48XXXYYYZZZ",
+    message: "Test API SMS",
+    creator: "Panel"
+  })
+});
+
+const data = await response.json();
+console.log(response.status, data);
+
+ +
+

6. Odpowiedzi API

+ +

6.1 Sukces (HTTP 200)

+
{
+  "status": "success",
+  "message": "SMS dodany do kolejki."
+}
+ +

6.2 Błędy

+ + + + + + + + + + + + + + +
HTTPKomunikatZnaczenie
400Brak danych JSON w żądaniu.Puste body.
400Niepoprawny JSON.Niepoprawna składnia JSON.
400Brakuje pola 'to' lub 'message'.Brak wymaganych pól.
400Pole to ma nieprawidłowy format.Nieprawidłowy numer telefonu.
400Wiadomość jest zbyt długa.Przekroczono limit długości message.
400Pole creator jest zbyt długie.Przekroczono limit długości creator.
401Nieautoryzowany dostęp.Błędny lub pusty klucz API.
405Dozwolona jest tylko metoda POST.Użyto innej metody HTTP.
500Błąd połączenia z bazą danych.Problem z połączeniem do DB.
500Nie udało się dodać SMS-a.Błąd przygotowania/wykonania zapytania SQL.
+
+ +
+

7. Dobre praktyki po stronie klienta

+
    +
  • Wysyłaj requesty wyłącznie przez HTTPS.
  • +
  • Maskuj api_key w logach aplikacyjnych.
  • +
  • Waliduj numer telefonu przed wysłaniem żądania.
  • +
  • Przy błędach HTTP 400 nie ponawiaj automatycznie bez korekty danych.
  • +
  • Przy błędach HTTP 500 wdroż retry z limitem i backoff.
  • +
+
+ +
+

8. Checklista testów integracyjnych

+
    +
  • Poprawny request zwraca HTTP 200 i status success.
  • +
  • Brak pola to lub message zwraca HTTP 400.
  • +
  • Niepoprawny numer to zwraca HTTP 400.
  • +
  • Za długi message zwraca HTTP 400.
  • +
  • Niepoprawny api_key zwraca HTTP 401.
  • +
  • Żądanie metodą GET zwraca HTTP 405.
  • +
+
+
+ + diff --git a/doc/stitch_softspm_academy_consulting/hydro_tech_intelligence/DESIGN.md b/doc/stitch_softspm_academy_consulting/hydro_tech_intelligence/DESIGN.md new file mode 100644 index 0000000..c428532 --- /dev/null +++ b/doc/stitch_softspm_academy_consulting/hydro_tech_intelligence/DESIGN.md @@ -0,0 +1,169 @@ +--- +name: Hydro-Tech Intelligence +colors: + surface: '#101415' + surface-dim: '#101415' + surface-bright: '#363a3b' + surface-container-lowest: '#0b0f10' + surface-container-low: '#191c1e' + surface-container: '#1d2022' + surface-container-high: '#272a2c' + surface-container-highest: '#323537' + on-surface: '#e0e3e5' + on-surface-variant: '#c2c6d6' + inverse-surface: '#e0e3e5' + inverse-on-surface: '#2d3133' + outline: '#8c909f' + outline-variant: '#424654' + surface-tint: '#b1c5ff' + primary: '#b1c5ff' + on-primary: '#002c70' + primary-container: '#0057cd' + on-primary-container: '#cbd8ff' + inverse-primary: '#0057cd' + secondary: '#4fdbc8' + on-secondary: '#003731' + secondary-container: '#04b4a2' + on-secondary-container: '#003f38' + tertiary: '#bec6e0' + on-tertiary: '#283044' + tertiary-container: '#565e74' + on-tertiary-container: '#d0d8f2' + error: '#ffb4ab' + on-error: '#690005' + error-container: '#93000a' + on-error-container: '#ffdad6' + primary-fixed: '#d9e2ff' + primary-fixed-dim: '#b1c5ff' + on-primary-fixed: '#001946' + on-primary-fixed-variant: '#00419d' + secondary-fixed: '#71f8e4' + secondary-fixed-dim: '#4fdbc8' + on-secondary-fixed: '#00201c' + on-secondary-fixed-variant: '#005048' + tertiary-fixed: '#dae2fd' + tertiary-fixed-dim: '#bec6e0' + on-tertiary-fixed: '#131b2e' + on-tertiary-fixed-variant: '#3f465c' + background: '#101415' + on-background: '#e0e3e5' + surface-variant: '#323537' + deep-ocean: '#0B1E3B' + electric-teal: '#2DD4BF' + warning-red: '#B91830' + surface-dark: '#020617' +typography: + display-lg: + fontFamily: Space Grotesk + fontSize: 48px + fontWeight: '700' + lineHeight: '1.1' + letterSpacing: -0.02em + display-lg-mobile: + fontFamily: Space Grotesk + fontSize: 36px + fontWeight: '700' + lineHeight: '1.2' + headline-lg: + fontFamily: Space Grotesk + fontSize: 32px + fontWeight: '600' + lineHeight: '1.2' + headline-md: + fontFamily: Space Grotesk + fontSize: 24px + fontWeight: '600' + lineHeight: '1.3' + body-lg: + fontFamily: Hanken Grotesk + fontSize: 18px + fontWeight: '400' + lineHeight: '1.6' + body-md: + fontFamily: Hanken Grotesk + fontSize: 16px + fontWeight: '400' + lineHeight: '1.5' + label-sm: + fontFamily: Hanken Grotesk + fontSize: 12px + fontWeight: '600' + lineHeight: '1.2' + letterSpacing: 0.05em +rounded: + sm: 0.25rem + DEFAULT: 0.5rem + md: 0.75rem + lg: 1rem + xl: 1.5rem + full: 9999px +spacing: + unit: 8px + container-max: 1280px + gutter: 24px + margin-mobile: 16px + margin-desktop: 40px + stack-sm: 12px + stack-md: 24px + stack-lg: 48px +--- + +## Brand & Style + +The visual identity of the design system is rooted in the "Hydro-Tech" aesthetic—a sophisticated blend of deep-sea professionality and high-tech innovation. It is designed to serve as an authoritative portal for the municipal water industry, balancing the reliability of institutional knowledge with the forward-thinking nature of modern consulting. + +The design style is **Corporate Modern with Experimental Accents**. It utilizes a dark, data-centric interface that mimics a high-end control center or command dashboard. By moving away from standard light-mode "document" styles, the design system positions the platform as a high-value intelligence tool. The emotional response should be one of "Trusted Innovation"—feeling as established as a 2014 industry leader while appearing technologically superior to legacy competitors. Expect structured layouts, precision-engineered components, and a focus on content hierarchy that makes dense technical information (webinars, courses, consulting reports) easy to digest. + +## Colors + +The palette is anchored by **#0057CD**, a deep, vibrant blue that signals trust and industry authority. This is complemented by **Electric Teal**, used sparingly as an "Experimental" accent to represent flow, clean water, and digital innovation. + +- **Primary Blue:** Used for key actions, brand moments, and critical navigation. +- **Electric Teal:** Reserved for innovation highlights, active states, and "Live" indicators for webinars. +- **Neutrals:** The background is a sophisticated `surface-dark` (#020617) to provide a high-contrast environment for white text and blue components. +- **Success/Warning:** Red is strictly reserved for urgent municipal alerts or critical data errors, ensuring its impact is never diluted. + +## Typography + +This design system employs a dual-sans-serif approach to distinguish between "Technical Data" and "Human Knowledge." + +**Space Grotesk** is used for all headlines and display elements. Its geometric, slightly eccentric letterforms evoke a sense of future-proofing and engineering precision. It is the voice of the platform's technological edge. + +**Hanken Grotesk** serves as the workhorse for body copy and data labels. It offers exceptional legibility in dark mode and maintains a professional, neutral tone that doesn't fatigue the eye during long-form reading of consulting reports or course materials. + +- **Scale:** Large display sizes use tight tracking to maintain a "journalistic" authority. +- **Hierarchy:** Use labels in uppercase with generous tracking to categorize content types (e.g., WEBINAR, ARTICLE, CONSULTING). + +## Layout & Spacing + +The layout philosophy follows a **Structured Fluid Grid**. It is designed to handle high-density information without feeling cluttered. + +- **Grid System:** A 12-column grid for desktop with 24px gutters. Elements should align strictly to this grid to reinforce the "engineered" feel of the platform. +- **Vertical Rhythm:** Spacing is strictly based on 8px increments. Use `stack-lg` for section breaks and `stack-sm` for related content within cards. +- **Content Density:** In data-rich views (like industry portals), use the 8px unit to create tight, information-dense components. In educational/marketing views (webinar landing pages), increase padding to 1.5x the standard unit to create "breathing room." + +## Elevation & Depth + +This design system avoids traditional heavy drop shadows, opting instead for **Tonal Layering and Inner Glows** to create depth within a dark environment. + +1. **Base Layer:** The deepest background (#020617). +2. **Surface Layer:** Cards and containers use #0F172A. These are differentiated from the base by a subtle 1px stroke (opacity 10%) using the Primary Blue. +3. **Active Elevation:** When an element is hovered or focused, it does not "rise" with a shadow. Instead, it gains a subtle teal inner-glow or an increased stroke opacity. +4. **Glassmorphism:** Use backdrop-blur (12px) for sticky navigation bars and floating action menus to maintain a sense of context and "fluidity." + +## Shapes + +The shape language is **Technical and Balanced**. Using a `roundedness` level of **2** (0.5rem base) provides enough softness to feel modern and accessible, without appearing "consumer-grade" or overly casual. + +- **Standard Radius:** 8px for buttons and input fields. +- **Container Radius:** 16px (rounded-lg) for cards and modular blocks. +- **Accent Shapes:** Use 45-degree clipped corners on decorative elements or specific "Experimental" UI callouts to mimic architectural or hydrodynamic blueprints. + +## Components + +- **Buttons:** Primary buttons use a solid #0057CD fill with white text. Secondary buttons are "Ghost" style with a teal border. Buttons should feel tactile and solid. +- **Cards:** Industry news and course cards use the surface-layer color with a top-accent border in Teal or Blue depending on the category. Use high-contrast headings within cards. +- **Input Fields:** Dark-themed inputs with 1px borders. On focus, the border transitions to Primary Blue with a subtle outer glow (glow color: primary color at 20% opacity). +- **Chips/Badges:** Small, high-contrast badges for "New," "Live," or "Pro." Use #14B8A6 for educational status and #0057CD for consulting-related tags. +- **Lists:** Use "Zebra" striping with very subtle color shifts for data-heavy portals to maintain line-tracking legibility. +- **Progress Indicators:** For courses and webinars, use a "thin-line" progress bar in Electric Teal, contrasting sharply against the dark background. \ No newline at end of file diff --git a/doc/stitch_softspm_academy_consulting/softspm_konsulting_i_doradztwo/code.html b/doc/stitch_softspm_academy_consulting/softspm_konsulting_i_doradztwo/code.html new file mode 100644 index 0000000..3b8f2ac --- /dev/null +++ b/doc/stitch_softspm_academy_consulting/softspm_konsulting_i_doradztwo/code.html @@ -0,0 +1,331 @@ + + + + + +SoftSPM Consulting - Municipal Water Solutions + + + + + + + + + + + +
+ +
+
+
+Hydro-Tech Advisory +

Intelligent Water Infrastructure Consulting

+

+ Empowering municipal water utilities since 2014. We combine deep institutional knowledge with advanced data analytics to optimize operations, ensure compliance, and drive sustainable growth in the water sector. +

+
+ + +
+
+
+ +
+
+
+ +
+
+water_drop +

Obszary Doradztwa

+
+
+ +
+
+analytics +Data-Driven +
+

Audyty Operacyjne

+

Kompleksowa analiza wydajności sieci wodociągowych z wykorzystaniem zaawansowanej telemetrii i modelowania hydraulicznego.

+
+ +
+
+gavel +Compliance +
+

Zgodność Regulacyjna

+

Doradztwo prawne i techniczne w zakresie nowych dyrektyw unijnych dotyczących jakości wody i odprowadzania ścieków.

+
+ +
+
+eco +Sustainability +
+

Strategie Zrównoważone

+

Opracowywanie wieloletnich planów inwestycyjnych redukujących ślad węglowy i optymalizujących zużycie energii w procesach uzdatniania.

+
+
+
+ +
+
+group +

Nasi Eksperci

+
+
+ +
+
+ +
+
+

Dr inż. Anna Kowalska

+Główny Inżynier Procesu +

15 lat doświadczenia w optymalizacji stacji uzdatniania wody. Autorka licznych publikacji branżowych.

+
+
+ +
+
+ +
+
+

Michał Nowak

+Dyrektor ds. Analizy Danych +

Ekspert w zakresie machine learningu stosowanego w detekcji wycieków i predykcji awarii sieci.

+
+
+
+
+ +
+
+history_edu +

Wybrane Realizacje (od 2014)

+
+
+ +
+
+Wodociągi Metropolitalne +Modernizacja systemu SCADA dla ujęcia powierzchniowego +
+
+2023 +arrow_forward +
+
+ +
+
+ZWiK Północ +Audyt energetyczny procesów napowietrzania osadu czynnego +
+
+2021 +arrow_forward +
+
+ +
+
+Regionalny Zakład Gospodarki Wodnej +Wdrożenie strategii zarządzania ryzykiem zgodnie z normą ISO 31000 +
+
+2018 +arrow_forward +
+
+
+
+ +
+
+

Umów bezpłatną konsultację

+

Porozmawiajmy o wyzwaniach Twojego przedsiębiorstwa wodociągowego. Nasi inżynierowie są gotowi przeprowadzić wstępną analizę.

+
+
+mail +consulting@softspm.com +
+
+phone ++48 22 123 45 67 +
+
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+ + + \ No newline at end of file diff --git a/doc/stitch_softspm_academy_consulting/softspm_konsulting_i_doradztwo/screen.png b/doc/stitch_softspm_academy_consulting/softspm_konsulting_i_doradztwo/screen.png new file mode 100644 index 0000000..0d7f765 Binary files /dev/null and b/doc/stitch_softspm_academy_consulting/softspm_konsulting_i_doradztwo/screen.png differ diff --git a/doc/stitch_softspm_academy_consulting/softspm_panel_kursanta/code.html b/doc/stitch_softspm_academy_consulting/softspm_panel_kursanta/code.html new file mode 100644 index 0000000..84f7091 --- /dev/null +++ b/doc/stitch_softspm_academy_consulting/softspm_panel_kursanta/code.html @@ -0,0 +1,332 @@ + + + + + +Dashboard - SoftSPM + + + + + + + + + + + +
+ +
+
+

Welcome back, Engineer

+

Here is your daily municipal intelligence summary.

+
+
+
+search + +
+ +
+User Profile +
+
+
+ +
+ +
+
+

Active Training Modules

+View All arrow_forward +
+
+ +
+
+Advanced +water_damage +
+

Urban Flood Mitigation Systems

+

Module 4: Predictive modeling using IoT sensor data.

+
+
+Progress +65% +
+
+
+
+
+
+ +
+
+Core +architecture +
+

Smart Grid Water Distribution

+

Module 2: Analyzing pressure zones and reducing non-revenue water.

+
+
+Progress +30% +
+
+
+
+
+
+
+
+ +
+
+

Live Agenda

+
+
+
+ +
+
+
OCT
+
24
+
+
+
Future of Desalination Tech
+

schedule 14:00 GMT

+
+
+ +
+
+
OCT
+
28
+
+
+
Regulatory Compliance 2025
+

schedule 10:00 GMT

+
+
+ +
+
+
NOV
+
02
+
+
+
Q&A: Infrastructure Funding
+

schedule 16:30 GMT

+
+
+
+ +
+
+ +
+

Recent Intelligence Reports

+
+ +
+
+
+
+PDF Report +
+
+

Q3 Water Quality Index

+

Comprehensive analysis of municipal testing data across 50 key metropolitan areas.

+
+
+ +
+
+
+
+Case Study +
+
+

Desalination Efficiency

+

Review of energy consumption metrics in the new coastal facility implementations.

+
+
+ +
+folder_open +Browse All Documents +
+
+
+
+
+ \ No newline at end of file diff --git a/doc/stitch_softspm_academy_consulting/softspm_panel_kursanta/screen.png b/doc/stitch_softspm_academy_consulting/softspm_panel_kursanta/screen.png new file mode 100644 index 0000000..a3cc26a Binary files /dev/null and b/doc/stitch_softspm_academy_consulting/softspm_panel_kursanta/screen.png differ diff --git a/doc/stitch_softspm_academy_consulting/softspm_strona_g_wna/code.html b/doc/stitch_softspm_academy_consulting/softspm_strona_g_wna/code.html new file mode 100644 index 0000000..6a05e1a --- /dev/null +++ b/doc/stitch_softspm_academy_consulting/softspm_strona_g_wna/code.html @@ -0,0 +1,311 @@ + + + + + +SoftSPM - Ekspercka wiedza dla branży wodociągowej + + + + + + + + + + +
+ +
+
+
+
+
+
+
+verified + Liderzy branży od 2014 +
+

+ Ekspercka wiedza dla branży wodociągowej. +

+

+ Dostarczamy zaawansowane analizy, szkolenia i nowoczesne rozwiązania konsultingowe dla przedsiębiorstw komunalnych i inżynierów środowiska. +

+
+ + +
+
+
+ +
+
+
+

Obszary Konsultingu

+

Kompleksowe wsparcie strategiczne i operacyjne.

+
+
+
+ +
+
+water_drop +

Zarządzanie Zapasami Wody

+

Optymalizacja procesów dystrybucji i redukcja strat w sieciach przesyłowych przy użyciu zaawansowanych modeli matematycznych.

+
+ +
+analytics +

Audyty Sieci

+

Szczegółowe analizy wydajności infrastruktury podziemnej.

+
+ +
+engineering +

Modernizacja Stacji

+

Projekty modernizacji SUW z naciskiem na automatyzację.

+
+ +
+
+eco +

Rozwiązania Zrównoważone

+

Wdrażanie technologii proekologicznych w gospodarce wod-kan.

+
+ +
+
+
+ +
+ +
+
+

Nadchodzące Webinaria

+
+
+ +
+
+15 +Lis +
+
+
LIVE
+

Detekcja Wycieków: Nowe Technologie

+

10:00 - 11:30 | Prowadzący: Dr. A. Nowak

+
+
+ +
+
+22 +Lis +
+
+

Prawne Aspekty Zrzutów Ścieków

+

13:00 - 15:00 | Prowadzący: Mec. K. Wiśniewski

+
+
+
+ +
+ +
+
+

Nowe Kursy E-learningowe

+Zobacz wszystkie arrow_forward +
+
+ +
+
+
+
+
Nowość
+
+
+

Hydraulika Sieci Wodociągowych w Praktyce

+

+schedule 12h materiałów + school Poziom: Średni +

+ +
+
+
+
+
+ +
+
+
+
+
+
+

Analiza Danych Jakości Wody (SCADA)

+

+schedule 8h materiałów + school Poziom: Zaawansowany +

+
+
+
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/doc/stitch_softspm_academy_consulting/softspm_strona_g_wna/screen.png b/doc/stitch_softspm_academy_consulting/softspm_strona_g_wna/screen.png new file mode 100644 index 0000000..9ffcede Binary files /dev/null and b/doc/stitch_softspm_academy_consulting/softspm_strona_g_wna/screen.png differ diff --git a/doc/stitch_softspm_academy_consulting/softspm_szkolenia_i_kursy/code.html b/doc/stitch_softspm_academy_consulting/softspm_szkolenia_i_kursy/code.html new file mode 100644 index 0000000..839aae9 --- /dev/null +++ b/doc/stitch_softspm_academy_consulting/softspm_szkolenia_i_kursy/code.html @@ -0,0 +1,255 @@ + + + + + +Katalog Szkoleń - SoftSPM + + + + + + + + + +
+
+
SoftSPM
+ +
+ + +
+
+
+
+ +
+

Katalog Szkoleń

+

Zdobądź specjalistyczną wiedzę z zakresu gospodarki wodnej. Wybierz spośród certyfikowanych kursów stworzonych przez ekspertów branżowych.

+
+ +
+ + + +
+ +
+
+Prawo +
+
+

Nowe Regulacje Wodno-Prawne 2024

+

Kompleksowe omówienie zmian w ustawie Prawo wodne oraz ich wpływu na działalność przedsiębiorstw wod-kan.

+
+
+schedule + 16 godzin +
+
+signal_cellular_alt + Średni +
+
+
+1200 PLN + +
+
+
+ +
+
+Technologia +
+
+

Systemy SCADA w Oczyszczalniach

+

Zaawansowany kurs z zakresu monitorowania i sterowania procesami w nowoczesnych oczyszczalniach ścieków.

+
+
+schedule + 24 godziny +
+
+signal_cellular_alt + Zaawansowany +
+
+
+2400 PLN + +
+
+
+
+
+ +
+
+
+

Dlaczego my?

+

Od 2014 roku SoftSPM dostarcza innowacyjne rozwiązania i specjalistyczną wiedzę dla sektora komunalnego. Nasze doświadczenie przekłada się na jakość szkoleń, które przygotowują do realnych wyzwań branży.

+
+
+10+ +Lat na rynku +
+
+5k+ +Absolwentów +
+
+
+
+ +
+
+
+
+
+ + + \ No newline at end of file diff --git a/doc/stitch_softspm_academy_consulting/softspm_szkolenia_i_kursy/screen.png b/doc/stitch_softspm_academy_consulting/softspm_szkolenia_i_kursy/screen.png new file mode 100644 index 0000000..ee83a8c Binary files /dev/null and b/doc/stitch_softspm_academy_consulting/softspm_szkolenia_i_kursy/screen.png differ diff --git a/images/ChatGPT _sm.png b/images/ChatGPT _sm.png new file mode 100644 index 0000000..1ad3499 Binary files /dev/null and b/images/ChatGPT _sm.png differ diff --git a/images/README.md b/images/README.md new file mode 100644 index 0000000..fac046d --- /dev/null +++ b/images/README.md @@ -0,0 +1,25 @@ +# Folder obrazów dla strony AquaNexis + +Ten folder zawiera wszystkie obrazy wykorzystywane na stronie internetowej AquaNexis. + +## Wymagane obrazy: + +1. **dashboard-mockup.png** - Obraz przedstawiający panel sterowania systemu IT bazując na https://scada.softspm.com/?page=operation&subpage=well&id=100 +2. **benefits.png** - Obraz ilustrujący korzyści z systemu +3. **company1.png** - Logo Przedsiębiorstwa Wodociągów i Kanalizacji w Krakowie +4. **company2.png** - Logo Zakładu Wodociągów i Kanalizacji w Poznaniu +5. **company3.png** - Logo Zakładu Gospodarki Wodnej w Gdańsku +6. **tech-diagram.png** - Schemat pokazujący komponenty technologiczne systemu AquaNexis +7. **system-architecture.png** - Szczegółowy diagram architektury systemu + +Zaleca się, aby wszystkie obrazy: +- Były w wysokiej rozdzielczości (minimum 1200px szerokości dla obrazów pełnoekranowych) +- Miały przezroczyste tło dla logo (format PNG) +- Były zoptymalizowane pod kątem szybkiego ładowania strony + +## Propozycje dla nowych obrazów: + +### tech-diagram.pngs +Diagram powinien przedstawiać kluczowe elementy systemu: czujniki w pompach, jednostki przetwarzające dane, chmurę i interfejs użytkownika. Najlepiej w nowoczesnym, schematycznym stylu z wykorzystaniem kolorów firmowych (niebieski, turkus). + +**WYGENEROWANO:** Nowoczesny diagram techniczny systemu AquaNexis, przedstawiający system centralny połączony za pomocą GSM i łączności satelitarnej z oddalonymi obiektami takimi jak studnie głębinowe i zbiorniki retencyjne. Diagram używa gradientów w kolorystyce niebiesko-turkusowej, z czytelnym oznaczeniem przepływu danych i elementami zabezpieczeń. diff --git a/images/ai-data-processing.jpg b/images/ai-data-processing.jpg new file mode 100644 index 0000000..cfecd5b Binary files /dev/null and b/images/ai-data-processing.jpg differ diff --git a/images/blog-1.jpg b/images/blog-1.jpg new file mode 100644 index 0000000..d1adbae Binary files /dev/null and b/images/blog-1.jpg differ diff --git a/images/blog-2.jpg b/images/blog-2.jpg new file mode 100644 index 0000000..46bae22 Binary files /dev/null and b/images/blog-2.jpg differ diff --git a/images/blog-3.jpg b/images/blog-3.jpg new file mode 100644 index 0000000..81767a0 Binary files /dev/null and b/images/blog-3.jpg differ diff --git a/images/blog-4.jpg b/images/blog-4.jpg new file mode 100644 index 0000000..983ab8e Binary files /dev/null and b/images/blog-4.jpg differ diff --git a/images/blog-5.jpg b/images/blog-5.jpg new file mode 100644 index 0000000..95c2e6c Binary files /dev/null and b/images/blog-5.jpg differ diff --git a/images/blog-6.jpg b/images/blog-6.jpg new file mode 100644 index 0000000..33df7cc Binary files /dev/null and b/images/blog-6.jpg differ diff --git a/images/blog-6_1.jpg b/images/blog-6_1.jpg new file mode 100644 index 0000000..7415c55 Binary files /dev/null and b/images/blog-6_1.jpg differ diff --git a/images/blog-6_2.jpg b/images/blog-6_2.jpg new file mode 100644 index 0000000..f8cdcb5 Binary files /dev/null and b/images/blog-6_2.jpg differ diff --git a/images/blog-7.jpg b/images/blog-7.jpg new file mode 100644 index 0000000..c58324d Binary files /dev/null and b/images/blog-7.jpg differ diff --git a/images/blog-7_01.jpg b/images/blog-7_01.jpg new file mode 100644 index 0000000..88b9a0f Binary files /dev/null and b/images/blog-7_01.jpg differ diff --git a/images/blog-7_02.jpg b/images/blog-7_02.jpg new file mode 100644 index 0000000..5f9a4c3 Binary files /dev/null and b/images/blog-7_02.jpg differ diff --git a/images/cloud-scada.jpg b/images/cloud-scada.jpg new file mode 100644 index 0000000..c749d8b Binary files /dev/null and b/images/cloud-scada.jpg differ diff --git a/images/dasboard_hn.png b/images/dasboard_hn.png new file mode 100644 index 0000000..e043a38 Binary files /dev/null and b/images/dasboard_hn.png differ diff --git a/images/dashboard-mockup.png b/images/dashboard-mockup.png new file mode 100644 index 0000000..f75fa38 Binary files /dev/null and b/images/dashboard-mockup.png differ diff --git a/images/energy-chart.jpg b/images/energy-chart.jpg new file mode 100644 index 0000000..f75fa38 Binary files /dev/null and b/images/energy-chart.jpg differ diff --git a/images/favicon.ico b/images/favicon.ico new file mode 100644 index 0000000..05a6703 Binary files /dev/null and b/images/favicon.ico differ diff --git a/images/iot-dashboard.jpg b/images/iot-dashboard.jpg new file mode 100644 index 0000000..389961b Binary files /dev/null and b/images/iot-dashboard.jpg differ diff --git a/images/iot-sensors.jpg b/images/iot-sensors.jpg new file mode 100644 index 0000000..e4fb313 Binary files /dev/null and b/images/iot-sensors.jpg differ diff --git a/images/mpwik-control-center.jpg b/images/mpwik-control-center.jpg new file mode 100644 index 0000000..9bd4ae7 Binary files /dev/null and b/images/mpwik-control-center.jpg differ diff --git a/images/mpwik-map.jpg b/images/mpwik-map.jpg new file mode 100644 index 0000000..e2a3c6c Binary files /dev/null and b/images/mpwik-map.jpg differ diff --git a/images/scada-mobile.jpg b/images/scada-mobile.jpg new file mode 100644 index 0000000..0244965 Binary files /dev/null and b/images/scada-mobile.jpg differ diff --git a/images/softspm_full.png b/images/softspm_full.png new file mode 100644 index 0000000..531338f Binary files /dev/null and b/images/softspm_full.png differ diff --git a/images/softspm_large.png b/images/softspm_large.png new file mode 100644 index 0000000..cd2bf05 Binary files /dev/null and b/images/softspm_large.png differ diff --git a/images/softspm_small.png b/images/softspm_small.png new file mode 100644 index 0000000..7b92c1c Binary files /dev/null and b/images/softspm_small.png differ diff --git a/images/spm_logo_new.png b/images/spm_logo_new.png new file mode 100644 index 0000000..cbd9435 Binary files /dev/null and b/images/spm_logo_new.png differ diff --git a/images/tech-diagram.png b/images/tech-diagram.png new file mode 100644 index 0000000..fd9eeb6 Binary files /dev/null and b/images/tech-diagram.png differ diff --git a/index-en.html b/index-en.html new file mode 100644 index 0000000..f1d35cf --- /dev/null +++ b/index-en.html @@ -0,0 +1,513 @@ + + + + + +SoftSPM - training, consulting and custom software for the water sector + + + + + + + + +
+ + +
+
+
+

Water sector know-how since 2014.

+

Training, webinars, consulting and turnkey software for municipal water and wastewater utilities. We teach what we do ourselves - we have been building software for over 25 years, and doing it for the water sector since 2014. We run the Wodociagowiec.pl portal and Gielda Komunalna, a free marketplace where the sector buys, sells and swaps equipment.

+ +
+ 25+ years in IT + In wod-kan since 2014 + 480+ people trained + Rollouts in PL and abroad +
+
+
+ + Training room: instructor at a data screen with participants at workstations +
Fig. 01 - Groups of up to 12, exercises on real documents and data
+
+
+
+ + +
+
+ +
+ SoftSPM - by the numbers + SPM-2026 + Rev A + Sheet 01 of 06 +
+ + + + + + + + + + + + +
No.ItemValueNotes
01Experience in IT25+ yearsFrom desktop systems to mobile applications
02In the water sectorsince 201412 years working with municipal utilities
03Specialists trained480+Classroom and online
04Webinars per year24On our own course platform
05Advisory projects60+Tariffs, organisation, procurement, digitisation
06Ecosystem reach2 servicesWodociagowiec.pl trade portal and Gielda Komunalna marketplace
+

Sample figures - to be confirmed before publication.

+
+
+ + +
+ 02 · Offer +
+
+

Four ways to draw on our experience

+

From an application built to order down to a single two-hour webinar. The same people deliver all of it - what comes up in a client rollout ends up in the next training session.

+
+
+
+ + 02.1 + +

Turnkey software

+

We build software to order: web systems and mobile applications shaped by the client's requirements. We also advise on large integrated system rollouts. Over 25 years in IT, with rollouts in Poland and abroad.

+ See how we work → +
+
+ + 02.2 + +

Consulting

+

Support with tariff applications, organisational reviews, procurement preparation and digitisation planning. The report ends with a list of decisions carrying costs and deadlines, not a set of generalities.

+ Ask about consulting → +
+
+ + 02.3 + +

Training

+

Classroom and online, in groups of up to 12. Regulation and tariffs, utility management, public procurement, customer service, digitisation. Exercises on real documents and data, not a slide deck.

+ Ask about the catalogue → +
+
+ + 02.4 + +

Webinars

+

Two sessions a month, two hours each: regulatory changes, cases from the sector, questions from participants. Live with a Q&A, then available as a recording on the course platform.

+ See the platform → +
+
+
+ + +
+ 03 · Subject tracks +
+
+

Eight areas the sector asks about most

+

We build the programme from what actually reaches the Wodociagowiec editorial desk and from what the sector is looking for on Gielda Komunalna. The first four tracks cover regulation and organisation, the next three cover the systems you run all of it on, and the last one ties the two together into a digitisation plan. If a question keeps coming back, it becomes a course.

+
+
+ +
+ + Tariff document, statutory symbol and approval stamp +
+
Track 01Regulation
+

Water law and the tariff application

+

Regulatory changes and their operational consequences, calculating necessary revenue, preparing and defending the tariff application before the regulator, reporting duties and water quality obligations.

+
2 days · 16 hClassroom / online
+
+
+ +
+ + Financial indicators and cost structure +
+
Track 02Management
+

Running a water utility

+

Cost accounting and the real price per m³, efficiency indicators and benchmarking, multi-year development plans, making the case for investment to the board and the municipal owner.

+
2 days · 16 hClassroom / online
+
+
+ +
+ + Comparing tenders against award criteria +
+
Track 03Procurement
+

Public procurement in practice

+

Writing a specification that does not end in a variation order, non-price criteria and life-cycle cost, evaluating tenders, and the appeal grounds that come up most often - and how to avoid them.

+
2 days · 16 hClassroom / online
+
+
+ +
+ + Customer request, billing and communication +
+
Track 04Customer service
+

Customer service, contracts and billing

+

Customer contracts and enforcement, complaints and difficult conversations, debt recovery step by step, billing with remote meter reading, and communication during outages.

+
1 day · 8 hClassroom / online
+
+
+ +
+ +
+ +
+ + Control room desk: network assets, a measurement trend and an alarm indicator +
+
Track 05SCADA
+

Managing and monitoring SCADA systems

+

How to read a control room desk without drowning in alarms: thresholds and hysteresis instead of false reports, event priorities, on-call and escalation procedures, measurement archiving, and board reports built from the same data the operator works on.

+
2 days · 16 hClassroom / online
+
+
+ +
+ + Security audit: NIS2 compliance shield and a list of vulnerabilities by risk level +
+
Track 06Security
+

Security audits and NIS2 compliance

+

Water utilities are critical infrastructure, so NIS2 obliges rather than suggests. Inventory of systems and access rights, separating the office network from the technology network, backups that can actually be restored, an incident response plan and reporting duties.

+
2 days · 16 hClassroom / online
+
+
+ +
+ +
+
+ +
+ A data bus linking ERP, CRM, TAX and billing to reporting and applications +
+
Track 07Integration
+

Integration with ERP, CRM and tax systems

+

The most common digitisation problem is not “which system should we buy”, it is “why do these figures disagree”. We teach how to plan data exchange between ERP, CRM, tax systems and billing: which source owns which piece of information, how to reconcile balances and customer records, how batch integration differs from an API, how to write integration requirements into a tender, and how to test acceptance.

+
2 days · 16 hClassroom / onlineLevel: advanced
+
+
+
+
+ +
+
+ +
+
+
Track 08Digitisation
+

Digitising a water utility

+

The track that closes the other seven: it turns knowledge of regulation, organisation and systems into a single plan of action. From paper to system - asset inventory, work-order and document flow, integrating billing with meter readings, selecting a supplier and writing requirements, and a realistic rollout plan split across budget years. For the people who have to plan it and defend the plan to the board.

+
2 days · 16 hClassroom / onlineLevel: intermediate
+
+ From paper documentation to a central system with modules and a phased rollout plan +
+
+
+
+ + +
+ 04 · Schedule +
+
+

Dates for the next edition are being finalised

+

These are the topics entering the programme first. We publish dates once the room and the trainers are confirmed - usually six weeks ahead. Leave your details against a topic and we will come back to you before we announce it publicly.

+
+
+ +
+ Topics in preparation + SPM-2026 + Rev A + Sheet 02 of 06 +
+ + + + + + + + + + + + + + +
TimingTopicFormatLocationRegistration
SoonTariff application - preparing and defending itTrainingBełchatówNotify me →
SoonWater law changes - what affects utilitiesWebinarOnlineNotify me →
SoonNon-price award criteria - workshopWebinarOnlineNotify me →
SoonDigitising a water utilityTrainingOnlineNotify me →
SoonTariffs 2027 - calculation and common errorsWebinarOnlineNotify me →
SoonDebt recovery and the difficult customerTrainingBełchatówNotify me →
SoonSecurity audit of your systems - where to startTrainingBełchatówNotify me →
SoonNIS2 and critical infrastructure - what utilities must doWebinarOnlineNotify me →
+

Dates for the next edition are being finalised. Leave your details against a topic and we will let you know as soon as registration opens.

+
+
+ + +
+ 05 · Course platform +
+
+
+

Webinars and e-learning in one place

+

Our own platform for running courses: live webinars, on-demand courses, tests and certificates. The material stays with you - come back to the recordings and documents whenever you need them.

+
    +
  • Live webinars with chat and a Q&A session
  • +
  • Recordings available indefinitely with your access
  • +
  • Downloadable materials: document templates, spreadsheets, checklists
  • +
  • Assessment tests and a completion certificate for your employer
  • +
  • Learner dashboard with progress and your team's training history
  • +
  • Company account - one subscription for many employees
  • +
+ Go to the platform +
+
+ + Learner dashboard: course progress, webinar agenda and a live indicator +
Fig. 02 - Learner dashboard: progress and upcoming webinars
+
+
+
+ + +
+ 06 · Turnkey software +
+
+

We build software to order

+

The course platform we run our webinars on, the Wodociagowiec portal and Gielda Komunalna are all our own builds. We do the same work for clients: analyse the need, design it, write it and hand over a working system. Turnkey, with no subcontractors. We also advise on large integrated system rollouts - on the buyer's side of the table, not the vendor's.

+
+ +
+
+

Over 25 years in IT

+

We started before “app” meant something on a phone. Since 2014 we have combined that with hands-on knowledge of the water sector, so nobody has to explain what a tariff, a meter reading or a service order is. We understand the domain and we write the code - a rare combination.

+
    +
  • Web systems: dashboards, registers, document workflows, reporting
  • +
  • Mobile apps for Android and iOS - a field crew app for work on site, offline too
  • +
  • Integration with what you already run - billing, GIS, the electronic operations logbook, accounting, well and pump operation, and network-wide monitoring
  • +
  • Data migration from legacy systems and spreadsheets
  • +
  • Advisory on large integrated system rollouts - ERP, GIS, billing
  • +
  • Rollout, user training and support after go-live
  • +
  • Code and documentation stay with the client - no vendor lock-in
  • +
  • Knowledge and experience backed by rollouts in Poland and abroad
  • +
+ Tell us about your project +
+
+ + From client requirements to a web system and a field crew app: analysis, design, development, testing, rollout +
Fig. 03 - From requirements to rollout: web system and field crew app
+
+
+ +
+ +
+ Turnkey delivery - scope and terms + SPM-2026 + Rev A + Sheet 03 of 06 +
+ + + + + + + + + + + + + +
No.ItemValueNotes
01Experience in IT25+ yearsFrom desktop systems to mobile applications
02Engagement modelturnkeyAnalysis, design, code, testing, rollout and support
03Platformsweb + mobileBrowser, Android, iOS - one shared data set
04Quote and scheduleup frontFixed scope and deadline agreed in the contract
05Rights to the codeclientSource code and documentation handed over in full
06Advisory roleclient sideOversight of large integrated system rollouts
07Rollout reachPL and abroadWell and pump operation systems, network monitoring
+

Scope is agreed case by case - the terms above are our standard way of working.

+
+
+ + +
+ 07 · Ecosystem +
+
+

A trade portal, a marketplace and a course platform - one loop

+

We do not start from a blank page. We run two services the Polish water sector uses daily, and they tell us what to teach. Readers' questions become webinar topics, listings on the marketplace show what the sector actually needs, and course participants meet the manufacturers and contractors they used to hunt for through word of mouth.

+
+ +
+ + Diagram: the Wodociagowiec.pl portal and the Gielda Komunalna marketplace exchange topics, practitioners and listings with the course platform at the centre +
Diagram 01 - The knowledge loop: the portal and the marketplace feed the platform, graduates return to the market
+
+ +
+
+ + Industry article with a chart and reader comments +
+
PortalKnowledge
+

Wodociagowiec.pl

+

An industry portal for water professionals: technical and legal articles, operational cases, analysis of regulatory change, reader questions and expert answers. This is where we see which topics keep coming back - and build the webinar programme from them.

+
Articles and analysisReader discussion
+ Go to wodociagowiec.pl → +
+
+ +
+ + Marketplace: surplus stock listed free of charge and matched with manufacturers and contractors +
+
MarketplaceNew
+

Gielda Komunalna

+

A new marketplace for the municipal sector. List, sell, buy or swap what is sitting in your warehouse - free of charge: surplus from completed projects, equipment replaced during upgrades, spare parts for machines nobody manufactures any more. It also brings together the manufacturers and contractors of a genuinely niche, specialist trade.

+
Free to listBuy · sell · swap
+ Go to gieldakomunalna.pl → +
+
+
+ +
+
+ +

Topics from the market

+

A question that comes back on the portal for the third time in a month goes into the programme of the next webinar. We do not guess what the sector needs - we see it in the readership figures and in the questions.

+ Portal → Platform +
+
+ +

A trade that knows itself

+

Water and wastewater is a niche market: there is a handful of specialist manufacturers and contractors nationwide, each in a different region. The marketplace gathers them in one place, so nobody has to ask around.

+ Marketplace → Manufacturers and contractors +
+
+ +

Warehouse, not scrap

+

Surplus from a project two years ago sits there because throwing it out feels wrong and there is nobody to give it to. Someone a hundred kilometres away is looking for exactly that. The marketplace matches the two - no commission, no listing fee.

+ Warehouse → Second life +
+
+
+ + +
+
+
“We came for tariff training and left with a work plan for the whole application.”
+
- head of finance, municipal water utility (sample reference)
+
+
+ + +
+ 08 · Contact +
+
+ +

Tell us what you need

+

Training runs in Polish by default; English-language sessions and materials are available for closed courses on request. Write to us and we will come back within one working day.

+
+
+ + +
+ + +
+ Confirm you are not a robot +
+ + Slide to verify + + + + +
+
+ +

+

You can also write to us directly at info@softspm.com.

+ +
+
+
+ +
+ + + + + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..4c8cbd6 --- /dev/null +++ b/index.html @@ -0,0 +1,573 @@ + + + + + +SoftSPM - szkolenia, konsulting i aplikacje dla branży wod-kan + + + + + + + + +
+ + +
+
+
+

Wiedza wod-kan. W praktyce od 2014.

+

Szkolenia, webinaria, konsulting i aplikacje pod klucz dla przedsiębiorstw wodociągowych i komunalnych. Uczymy tego, co sami robimy - bo od ponad 25 lat piszemy oprogramowanie, a od 2014 roku robimy to dla branży wod-kan. Prowadzimy portal Wodociągowiec.pl oraz Giełdę Komunalną - bezpłatny marketplace, na którym branża kupuje, sprzedaje i wymienia sprzęt.

+ +
+ 25+ lat w IT + Od 2014 w wod-kan + 480+ przeszkolonych + Wdrożenia w PL i za granicą +
+
+
+ + Sala szkoleniowa: prowadzący przy ekranie z danymi i uczestnicy przy stanowiskach +
Rys. 01 - Szkolenie w grupie do 12 osób, ćwiczenia na realnych danych
+
+
+
+ + +
+
+ +
+ SoftSPM - doświadczenie w liczbach + SPM-2026 + Rev A + Arkusz 01 z 06 +
+ + + + + + + + + + + + +
NrPozycjaWartośćUwagi
01Doświadczenie w IT25+ latOd systemów desktopowych po aplikacje mobilne
02Na rynku wod-kanod 201412 lat pracy z przedsiębiorstwami komunalnymi
03Przeszkoleni specjaliści480+Szkolenia stacjonarne i online
04Webinariów rocznie24Na własnej platformie kursowej
05Projekty doradcze60+Organizacja, taryfy, cyfryzacja, zamówienia
06Zasięg ekosystemu2 serwisyPortal Wodociągowiec.pl i marketplace Giełda Komunalna
+

Wartości przykładowe - do potwierdzenia przed publikacją.

+
+
+ + +
+ 02 · Oferta +
+
+

Cztery sposoby, żeby skorzystać z naszego doświadczenia

+

Od aplikacji pisanej na zamówienie po pojedynczy webinar. Wszystko robią ci sami ludzie - to, co pojawia się we wdrożeniu u klienta, trafia potem na szkolenie.

+
+
+
+ + 02.1 + +

Aplikacje pod klucz

+

Piszemy oprogramowanie na zamówienie: systemy webowe i aplikacje mobilne budowane pod wymagania klienta. Doradzamy też przy wdrożeniach dużych systemów zintegrowanych. Ponad 25 lat w IT, wdrożenia w kraju i za granicą.

+ Zobacz, jak pracujemy → +
+
+ + 02.2 + +

Konsulting

+

Wsparcie przy wniosku taryfowym, przeglądzie organizacji, przygotowaniu postępowania i planie cyfryzacji. Raport kończy się listą decyzji z kosztami i terminami, nie zbiorem ogólników.

+ Zakres konsultingu → +
+
+ + 02.3 + +

Szkolenia

+

Stacjonarne i online, w grupach do 12 osób. Prawo i taryfy, zarządzanie przedsiębiorstwem, zamówienia publiczne, obsługa odbiorcy, cyfryzacja. Ćwiczenia na dokumentach i danych, nie wykład z prezentacji.

+ Katalog szkoleń → +
+
+ + 02.4 + +

Webinaria

+

Dwa spotkania w miesiącu, po dwie godziny: zmiany w przepisach, przypadki z branży, pytania od uczestników. Na żywo z sesją Q&A, potem jako nagranie na platformie kursowej.

+ Kalendarz webinariów → +
+
+
+ + +
+ 03 · Ścieżki tematyczne +
+
+

Osiem obszarów, w których branża najczęściej pyta

+

Program układamy z tego, co realnie spływa do redakcji Wodociągowca i z tego, czego branża szuka na Giełdzie Komunalnej. Pierwsze cztery ścieżki dotyczą prawa i organizacji, trzy kolejne - systemów, którymi to wszystko obsługujecie, a ostatnia spina jedno z drugim w plan cyfryzacji. Jeśli temat wraca co tydzień, trafia do katalogu szkoleń.

+
+
+ +
+ + Dokument taryfowy, paragraf i pieczęć zatwierdzenia +
+
Ścieżka 01Prawo i taryfy
+

Prawo wodne i wniosek taryfowy

+

Zmiany w przepisach i ich skutki operacyjne, przygotowanie i obrona wniosku taryfowego przed Wodami Polskimi, kalkulacja niezbędnych przychodów, sprawozdawczość i jakość wody.

+
2 dni · 16 hStacjonarnie / online
+ Zobacz program → +
+
+ +
+ + Wskaźniki finansowe i struktura kosztów przedsiębiorstwa +
+
Ścieżka 02Zarządzanie
+

Zarządzanie przedsiębiorstwem wod-kan

+

Rachunek kosztów i kalkulacja ceny m³, wskaźniki efektywności, plan wieloletni i uzasadnianie inwestycji przed radą, komunikacja z właścicielem samorządowym i mieszkańcami.

+
2 dni · 16 hStacjonarnie / online
+ Zobacz program → +
+
+ +
+ + Porównanie ofert i kryteria oceny w postępowaniu +
+
Ścieżka 03Zamówienia
+

Zamówienia publiczne w praktyce

+

Opis przedmiotu zamówienia, który nie kończy się aneksem, kryteria pozacenowe i koszt cyklu życia, ocena ofert, najczęstsze zarzuty odwoławcze i jak ich uniknąć.

+
2 dni · 16 hStacjonarnie / online
+ Zobacz program → +
+
+ +
+ + Zgłoszenie odbiorcy, rozliczenie i komunikacja +
+
Ścieżka 04Obsługa klienta
+

Obsługa odbiorcy, umowy i billing

+

Umowy z odbiorcami i ich egzekwowanie, reklamacje i trudne rozmowy, windykacja należności, odczyty zdalne i rozliczenia, standardy obsługi w biurze i w terenie.

+
1 dzień · 8 hStacjonarnie / online
+ Zobacz program → +
+
+ +
+ + +
+ +
+ + Pulpit dyspozytorski: obiekty sieci, trend pomiarowy i sygnalizacja alarmu +
+
Ścieżka 05SCADA
+

Zarządzanie i monitoring systemów SCADA

+

Jak czytać pulpit dyspozytorski i nie utonąć w alarmach: progi i histereza zamiast fałszywych zgłoszeń, priorytety zdarzeń, dyżury i eskalacja, archiwizacja pomiarów, raporty dla zarządu z tych samych danych, na których pracuje dyspozytor.

+
2 dni · 16 hStacjonarnie / online
+ Zobacz program → +
+
+ +
+ + Audyt bezpieczeństwa: tarcza zgodności NIS2 i lista podatności z poziomami ryzyka +
+
Ścieżka 06Bezpieczeństwo
+

Audyty bezpieczeństwa i zgodność NIS2

+

Sektor wodociągowy to infrastruktura krytyczna, więc NIS2 obowiązuje, a nie sugeruje. Inwentaryzacja systemów i dostępów, segmentacja sieci biurowej od technologicznej, kopie zapasowe, które da się odtworzyć, plan reakcji na incydent i obowiązki zgłoszeniowe.

+
2 dni · 16 hStacjonarnie / online
+ Zobacz program → +
+
+ +
+ +
+
+ +
+ Magistrala danych łącząca ERP, CRM, TAX i billing z raportowaniem oraz aplikacjami +
+
Ścieżka 07Integracje
+

Integracja z systemami klasy ERP, CRM i podatkowymi

+

Najczęstszy problem cyfryzacji nie brzmi „którego systemu użyć”, tylko „dlaczego te dane się nie zgadzają”. Uczymy, jak zaplanować wymianę danych między ERP, CRM, systemami podatkowymi i billingiem: co jest źródłem prawdy dla której informacji, jak uzgadniać salda i kartoteki, czym różni się integracja wsadowa od API, jak opisać wymagania integracyjne w postępowaniu i jak testować odbiór.

+
2 dni · 16 hStacjonarnie / onlinePoziom: zaawansowany
+ Zobacz program → +
+
+
+
+ +
+
+ +
+
+
Ścieżka 08Cyfryzacja
+

Cyfryzacja przedsiębiorstwa wod-kan

+

Ścieżka domykająca pozostałe: przekłada wiedzę z prawa, organizacji i systemów na jeden plan działania. Od papieru do systemu - inwentaryzacja majątku, obieg zleceń i dokumentów, integracja billingu z odczytami, dobór dostawcy i pisanie wymagań, realny harmonogram rozłożony na etapy budżetowe. Dla kadry, która ma to zaplanować i obronić przed zarządem.

+
2 dni · 16 hStacjonarnie / onlinePoziom: średni
+ Zobacz program → +
+ Od papierowej dokumentacji do systemu centralnego z modułami i etapowym planem wdrożenia +
+
+
+
+ + +
+ 04 · Harmonogram +
+
+

Terminy najbliższej edycji ustalamy

+

Poniżej tematy, które wchodzą do programu jako pierwsze. Daty podajemy, gdy domkniemy salę i skład prowadzących - zwykle z sześciotygodniowym wyprzedzeniem.

+
+
+ +
+ Tematy w przygotowaniu + SPM-2026 + Rev A + Arkusz 02 z 06 +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TerminTematFormaMiejsceZapisy
WkrótceWniosek taryfowy - przygotowanie i obronaSzkolenieBełchatówPowiadom mnie →
WkrótceZmiany w Prawie wodnym - co dotyczy przedsiębiorstwWebinarOnlinePowiadom mnie →
WkrótceKryteria pozacenowe w postępowaniu - warsztatWebinarOnlinePowiadom mnie →
WkrótceCyfryzacja przedsiębiorstwa wod-kanSzkolenieOnlinePowiadom mnie →
WkrótceTaryfy 2027 - kalkulacja i najczęstsze błędyWebinarOnlinePowiadom mnie →
WkrótceWindykacja i trudny odbiorca - warsztatSzkolenieBełchatówPowiadom mnie →
WkrótceAudyt bezpieczeństwa systemów - od czego zacząćSzkolenieBełchatówPowiadom mnie →
WkrótceNIS2 i infrastruktura krytyczna - obowiązki przedsiębiorstwaWebinarOnlinePowiadom mnie →
+

Ustalamy terminy najbliższej edycji. Zostaw kontakt przy wybranym temacie na stronie Szkolenia i webinaria - odezwiemy się, gdy otworzymy zapisy.

+
+
+ + +
+ 05 · Platforma kursowa +
+
+
+

Webinary i e-learning w jednym miejscu

+

Nasza własna platforma do prowadzenia kursów: webinaria na żywo, kursy on-demand, testy i certyfikaty. Materiał zostaje z Tobą - wracasz do nagrań i dokumentów, kiedy ich potrzebujesz.

+
    +
  • Webinaria na żywo z czatem i sesją pytań do prowadzącego
  • +
  • Nagrania dostępne bezterminowo w ramach wykupionego dostępu
  • +
  • Materiały do pobrania: wzory pism, arkusze kalkulacyjne, listy kontrolne
  • +
  • Testy sprawdzające i certyfikat ukończenia dla pracodawcy
  • +
  • Panel kursanta z postępem nauki i historią szkoleń całego zespołu
  • +
  • Konto firmowe - jeden dostęp dla wielu pracowników
  • +
+ Przejdź do platformy +
+
+ + Panel kursanta: postęp kursów, agenda webinariów i znacznik transmisji na żywo +
Rys. 02 - Panel kursanta: postęp nauki i nadchodzące webinaria
+
+
+
+ + +
+ 06 · Aplikacje pod klucz +
+
+

Budujemy oprogramowanie na zamówienie

+

Platforma kursowa, na której prowadzimy webinaria, portal Wodociągowiec i Giełda Komunalna - to nasze własne wdrożenia. Tę samą robotę wykonujemy dla klientów: analizujemy potrzebę, projektujemy, programujemy i oddajemy działający system. Pod klucz, bez podwykonawców. Doradzamy również przy wdrożeniach dużych systemów zintegrowanych - stojąc po stronie zamawiającego, nie dostawcy.

+
+ +
+
+

Ponad 25 lat w IT

+

Zaczynaliśmy, zanim „aplikacja” znaczyła coś w telefonie. Od 2014 roku łączymy to z praktyczną znajomością branży wod-kan, więc nie trzeba nam tłumaczyć, czym jest taryfa, odczyt czy zlecenie serwisowe. Rozumiemy dziedzinę i piszemy kod - to rzadkie połączenie.

+
    +
  • Systemy webowe: panele, ewidencje, obiegi dokumentów, raportowanie
  • +
  • Aplikacje mobilne na Androida i iOS - aplikacja brygady do pracy w terenie, także offline
  • +
  • Integracje z systemami, które już u Was działają - billing, GIS, elektroniczna książka eksploatacji, księgowość, eksploatacja ujęć i pomp oraz monitoring całej sieci
  • +
  • Migracja danych ze starych systemów i arkuszy kalkulacyjnych
  • +
  • Doradztwo przy wdrożeniach dużych systemów zintegrowanych - ERP, GIS, billing
  • +
  • Wdrożenie, szkolenie użytkowników i opieka serwisowa po starcie
  • +
  • Kod i dokumentacja zostają u klienta - bez uzależnienia od dostawcy
  • +
  • Wiedza i doświadczenie poparte wdrożeniami w Polsce i poza granicami
  • +
+ Opisz swój projekt +
+
+ + Od wymagań klienta do systemu webowego i aplikacji brygady: analiza, projekt, programowanie, testy, wdrożenie +
Rys. 03 - Od wymagań do wdrożenia: system webowy i aplikacja brygady
+
+
+ +
+ +
+ Realizacja pod klucz - zakres i warunki + SPM-2026 + Rev A + Arkusz 03 z 06 +
+ + + + + + + + + + + + + +
NrPozycjaWartośćUwagi
01Doświadczenie w IT25+ latOd systemów desktopowych po aplikacje mobilne
02Model współpracypod kluczAnaliza, projekt, kod, testy, wdrożenie i serwis
03Platformyweb + mobilePrzeglądarka, Android, iOS - wspólne dane
04Wycena i harmonogramprzed startemStały zakres i termin uzgodnione w umowie
05Prawa do koduklientKod źródłowy i dokumentacja przekazywane w komplecie
06Rola doradczapo stronie klientaNadzór nad wdrożeniem dużego systemu zintegrowanego
07Zasięg wdrożeńPL i zagranicaSystemy eksploatacji ujęć, pomp i monitoringu sieci
+

Zakres ustalamy indywidualnie - powyższe warunki to nasz standard współpracy.

+
+
+ + +
+ 07 · Ekosystem +
+
+

Portal, marketplace i platforma kursowa - jeden obieg

+

Nie zaczynamy od pustej kartki. Prowadzimy dwa serwisy, z których branża wod-kan korzysta na co dzień - i to one podpowiadają nam, czego uczyć. Pytania czytelników portalu stają się tematami webinariów, ogłoszenia na giełdzie pokazują, czego rynkowi realnie brakuje, a uczestnicy szkoleń poznają tam producentów i wykonawców, których wcześniej szukali po znajomych.

+
+ +
+ + Schemat: portal Wodociągowiec.pl i marketplace Giełda Komunalna wymieniają tematy, praktyków i ogłoszenia z platformą kursową w centrum +
Schemat 01 - Obieg wiedzy: portal i marketplace zasilają platformę kursową, absolwenci wracają na rynek
+
+ +
+
+ + Artykuł branżowy z wykresem i komentarzami czytelników +
+
PortalWiedza
+

Wodociągowiec.pl

+

Portal branżowy dla wodociągowców: artykuły techniczne i prawne, przypadki z eksploatacji, omówienia zmian w przepisach, pytania czytelników i odpowiedzi ekspertów. Stąd bierzemy tematy, które wracają najczęściej - i z nich układamy program webinariów.

+
Artykuły i analizyKomentarze branży
+ Przejdź do wodociagowiec.pl → +
+
+ +
+ + Marketplace: ogłoszenia sprzedaży i poszukiwane, kojarzenie stron +
+
MarketplaceNowość
+

Giełda Komunalna

+

Nowy marketplace dla branży komunalnej. Bezpłatnie wystawisz, sprzedasz, kupisz albo wymienisz to, co zalega w magazynie: nadwyżki z inwestycji, sprzęt po modernizacji, części do urządzeń, których nikt już nie produkuje. Rzecz leżąca u Ciebie bezużytecznie bywa dokładnie tym, czego ktoś szuka od pół roku.

+
Wystawianie za darmoKupno · sprzedaż · wymiana
+ Wystaw za darmo na gieldakomunalna.pl → +
+
+
+ +
+
+ +

Tematy z rynku

+

Pytanie, które wraca na portalu trzeci raz w miesiącu, trafia do programu najbliższego webinaru. Nie zgadujemy, czego branża potrzebuje - widzimy to w statystykach czytelnictwa i w zapytaniach.

+ Portal → Platforma +
+
+ +

Branża, która się zna

+

Wod-kan to rynek niszowy: producentów specjalistycznego osprzętu i wykonawców jest garstka, a każdy siedzi w innym województwie. Giełda zrzesza ich w jednym miejscu, żeby nie trzeba było szukać po znajomych.

+ Giełda → Producenci i wykonawcy +
+
+ +

Magazyn zamiast złomu

+

Nadwyżka z inwestycji sprzed dwóch lat leży, bo szkoda wyrzucić, a nie ma komu oddać. Ktoś sto kilometrów dalej szuka dokładnie tego. Giełda kojarzy obie strony - bez prowizji i bez opłat za wystawienie.

+ Magazyn → Drugie życie +
+
+
+ + +
+
+
„Przyszliśmy po szkolenie z taryf, a wyszliśmy z gotowym harmonogramem prac na cały wniosek.”
+
- kierownik działu ekonomicznego, PWiK (referencja przykładowa)
+
+
+ + +
+ 08 · Newsletter +
+
+ +

Raz w miesiącu, konkretnie

+

Terminy szkoleń i webinariów, krótkie omówienie zmian w przepisach i jedna rzecz z praktyki, którą warto sprawdzić u siebie. Bez spamu - branża nie jest impulsywna.

+
+
+ + +
+ + +
+ Potwierdź, że nie jesteś robotem +
+ + Przesuń, aby zweryfikować + + + + +
+
+ +

+

Raz w miesiącu, bez spamu. Rezygnacja w każdej chwili.

+ +
+
+
+ +
+ + + + + + + diff --git a/konsulting.html b/konsulting.html new file mode 100644 index 0000000..6202ad5 --- /dev/null +++ b/konsulting.html @@ -0,0 +1,358 @@ + + + + + +Konsulting dla przedsiębiorstw wod-kan - SoftSPM + + + + + + + + +
+ + +
+ 01 · Konsulting +
+

Doradztwo, które kończy się decyzją

+

Wspieramy przedsiębiorstwa wodociągowe i komunalne tam, gdzie sprawa jest zbyt duża na szkolenie: wniosek taryfowy, przegląd organizacji, przygotowanie postępowania, plan cyfryzacji, nadzór nad wdrożeniem dużego systemu, audyt bezpieczeństwa i uporządkowanie integracji. Raport zamykamy listą decyzji z kosztami i terminami - nie zbiorem rekomendacji do rozważenia.

+ +
+ + +
+ 02 · Zakres usług +
+
+

Siedem obszarów doradztwa

+

Każdy projekt zaczynamy od przeglądu dokumentów i rozmów z ludźmi, którzy wykonują pracę. Dopiero potem proponujemy rozwiązanie - także wtedy, gdy propozycją jest niezmienianie niczego. Cztery pierwsze obszary dotyczą organizacji i finansów, trzy kolejne - systemów, w których ta organizacja pracuje.

+
+
+ +
+ + 02.1 + +

Wniosek taryfowy - wsparcie i weryfikacja

+

Przegląd kalkulacji niezbędnych przychodów, spójność założeń z planem inwestycyjnym, kompletność uzasadnienia, przygotowanie na wezwania do uzupełnień. Prowadzimy też przez cały proces od zera, jeśli robicie to pierwszy raz w nowym składzie.

+ Zapytaj o wsparcie → +
+ +
+ + 02.2 + +

Przegląd organizacji i wskaźników

+

Gdzie w przedsiębiorstwie powstają koszty, których nikt nie przypisuje, kto dubluje czyjąś pracę, jakie wskaźniki naprawdę warto raportować zarządowi. Wynik: obraz sytuacji na jednej stronie plus lista zmian uszeregowana według efektu i trudności.

+ Zapytaj o przegląd → +
+ +
+ + 02.3 + +

Przygotowanie postępowania

+

Opis przedmiotu zamówienia napisany tak, żeby oferty dało się porównać, kryteria pozacenowe odporne na odwołania, warunki udziału proporcjonalne do zamówienia. Wsparcie przy ocenie ofert i odpowiedziach na pytania wykonawców.

+ Zapytaj o postępowanie → +
+ +
+ + 02.4 + +

Plan cyfryzacji

+

Mapa obiegu informacji: gdzie dane powstają, gdzie giną, co jest przepisywane ręcznie po raz trzeci. Dobór systemów docelowych, kolejność wdrożeń, realny harmonogram i budżet, opis przedmiotu zamówienia oraz wsparcie przy ocenie ofert dostawców.

+ Zapytaj o cyfryzację → +
+ +
+ + 02.5 + +

Wdrożenie dużego systemu zintegrowanego

+

Stoimy po stronie zamawiającego przy wdrożeniach ERP, GIS, billingu i systemów obiegu dokumentów. Weryfikujemy analizę przedwdrożeniową i harmonogram dostawcy, pilnujemy zakresu i odbiorów, wyłapujemy ryzyka migracji danych i integracji, zanim staną się aneksem. Ponad 25 lat w IT po to, żeby wiedzieć, o co pytać.

+ Zapytaj o nadzór → +
+ +
+ + 02.6 + +

Audyt bezpieczeństwa i przygotowanie do NIS2

+

Przegląd systemów, kont i dostępów, ocena segmentacji sieci biurowej i technologicznej, sprawdzenie, czy kopie zapasowe da się realnie odtworzyć. Wynik: lista podatności uszeregowana według ryzyka, plan reakcji na incydent i dokumentacja pod obowiązki sprawozdawcze wynikające z NIS2.

+ Zapytaj o audyt → +
+ +
+ + 02.7 + +

Integracje i spójność danych

+

Gdy ERP pokazuje co innego niż billing, a CRM co innego niż obie te rzeczy. Ustalamy, które źródło jest nadrzędne dla której informacji, projektujemy wymianę danych, opisujemy wymagania integracyjne do postępowania i przygotowujemy testy odbiorowe, które wykryją rozjazd, zanim zrobi się z niego problem księgowy.

+ Zapytaj o integracje → +
+ +
+
+ + +
+ 03 · Przebieg współpracy +
+
+

Trzy kroki, jasny wynik

+

Nie sprzedajemy abonamentu na doradztwo. Każdy projekt ma określony zakres, termin i produkt końcowy uzgodnione przed rozpoczęciem prac.

+
+
+
+ 01 +

Rozmowa wstępna

+

Bezpłatnie, około godziny, zdalnie lub u Was. Ustalamy, co dokładnie boli: presja taryfowa, spór z organem, chaos w dokumentach, planowana inwestycja. Kończymy jasną informacją, czy możemy pomóc i jakim nakładem - także wtedy, gdy odpowiedź brzmi „tego nie potrzebujecie”.

+
+
+ 02 +

Przegląd i rozmowy

+

Analiza dokumentów, danych i procedur oraz rozmowy z osobami, które wykonują pracę - od biura obsługi po dział techniczny. Szukamy różnicy między tym, co jest w regulaminie, a tym, co dzieje się naprawdę.

+
+
+ 03 +

Raport i decyzje

+

Dokument z ustaleniami, wariantami rozwiązań i wyliczonym efektem każdego z nich. Prezentacja dla zarządu lub rady nadzorczej w cenie. Po wdrożeniu wracamy i sprawdzamy, czy zmiana faktycznie zadziałała.

+
+
+
+ + +
+ 04 · Wybrane realizacje +
+
+
+ + Raport doradczy: analiza danych, porównanie wariantów i lista decyzji +
Rys. 04 - Od przeglądu dokumentów do listy decyzji z wyliczonym efektem
+
+
+

Co dostajecie na koniec

+
    +
  • Raport z ustaleniami i wskazaniem źródeł każdego wniosku
  • +
  • Warianty rozwiązań z kosztem, czasem i ryzykiem każdego z nich
  • +
  • Lista decyzji uszeregowana według efektu, nie według łatwości
  • +
  • Gotowe wzory dokumentów, jeśli zakres tego wymaga
  • +
  • Prezentacja dla zarządu lub rady nadzorczej w cenie projektu
  • +
  • Kontrolne spotkanie po wdrożeniu - sprawdzamy, czy zadziałało
  • +
+
+
+
+ +
+ Projekty doradcze - przekrój + SPM-2026 + Rev A + Arkusz 06 z 06 +
+ + + + + + + + + + + + + +
NrZakresEfektUwagi
01Wsparcie przy wniosku taryfowym14 dniSkrócenie czasu odpowiedzi na wezwanie organu
02Przegląd organizacji i wskaźników9 zmianLista decyzji przyjęta uchwałą zarządu
03Przygotowanie postępowania4 ofertyPorównywalne oferty zamiast jednej ważnej
04Plan cyfryzacji przedsiębiorstwa3 etapyWdrożenie rozłożone na 3 lata budżetowe
05Nadzór nad wdrożeniem systemu0 aneksówZakres i harmonogram utrzymane do odbioru końcowego
06Audyt bezpieczeństwaNIS2Lista podatności wg ryzyka i plan reakcji na incydent
07Uporządkowanie integracji1 źródło prawdyKoniec rozjazdu danych między ERP, billingiem i CRM
+

Wartości przykładowe - do potwierdzenia przed publikacją.

+
+
+ + +
+ 05 · Zespół +
+
+

Prowadzą praktycy

+

Ci sami ludzie, którzy prowadzą projekty doradcze, prowadzą też szkolenia i piszą na Wodociągowcu. Materiał na zajęcia bierze się z zeszłotygodniowego projektu, nie z podręcznika.

+
+
+
+ + Dokumenty taryfowe i regulacyjne +
+

Zespół prawno-taryfowy

+

Prawo wodne · taryfy

+

Wnioski taryfowe i postępowania przed organem regulacyjnym, sprawozdawczość, regulaminy dostarczania wody, umowy z odbiorcami. Autorzy analiz prawnych publikowanych na Wodociągowcu.

+
+
+
+ + Wskaźniki i struktura kosztów +
+

Zespół ekonomiczny

+

Koszty · wskaźniki · plany

+

Rachunek kosztów i kalkulacja ceny m³, wieloletnie plany rozwoju i modernizacji, uzasadnienia inwestycyjne dla zarządów i rad nadzorczych, benchmarking między przedsiębiorstwami.

+
+
+
+ + Porównanie ofert w postępowaniu +
+

Zespół zamówień i wdrożeń

+

Postępowania · cyfryzacja

+

Opisy przedmiotu zamówienia, kryteria oceny ofert, wsparcie komisji przetargowych, plany cyfryzacji i nadzór nad wdrożeniami systemów po stronie zamawiającego. Ci sami ludzie prowadzą audyty bezpieczeństwa i projektują integracje - bo na co dzień piszą oprogramowanie.

+
+
+
+

Skład i sylwetki ekspertów - do uzupełnienia danymi osobowymi przed publikacją.

+
+ + +
+
+
„Raport dostaliśmy w trzy tygodnie. Zarząd przyjął listę decyzji bez jednej poprawki.”
+
- prezes zarządu, przedsiębiorstwo wodociągowe (referencja przykładowa)
+
+
+ + +
+ 06 · Bezpłatna rozmowa wstępna +
+
+ +

Opisz sytuację - oddzwonimy

+

Godzina rozmowy bez zobowiązań. Jeśli po niej uznamy, że nie jesteśmy właściwym wykonawcą, powiemy to wprost i wskażemy, czego szukać.

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+ + +
+ Potwierdź, że nie jesteś robotem +
+ + Przesuń, aby zweryfikować + + + + +
+
+ +

+

Zgłoszenia możesz też wysłać bezpośrednio na info@softspm.com.

+ +
+
+
+ +
+ + + + + + diff --git a/softspm_page_2026.code-workspace b/softspm_page_2026.code-workspace new file mode 100644 index 0000000..876a149 --- /dev/null +++ b/softspm_page_2026.code-workspace @@ -0,0 +1,8 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": {} +} \ No newline at end of file diff --git a/szkolenia.html b/szkolenia.html new file mode 100644 index 0000000..673b397 --- /dev/null +++ b/szkolenia.html @@ -0,0 +1,427 @@ + + + + + +Szkolenia i webinaria dla wod-kan - SoftSPM + + + + + + + + +
+ + +
+ 01 · Szkolenia i webinaria +
+

Szkolenia z tego, o co branża pyta najczęściej

+

Program układamy z pytań, które spływają do redakcji Wodociągowca i z tego, czego branża szuka na Giełdzie Komunalnej. Uczymy prawa i organizacji, ale też systemów: SCADA, bezpieczeństwa i integracji - bo od ponad 25 lat sami je budujemy. Małe grupy, praca na dokumentach i danych, prowadzący z praktyki.

+
+ Grupy do 12 osób + Certyfikat ukończenia + Materiały bezterminowo + Wersja zamknięta dla firm +
+
+ + +
+ 02 · Format szkolenia +
+
+ +
+ Format i warunki uczestnictwa + SPM-2026 + Rev A + Arkusz 04 z 06 +
+ + + + + + + + + + + + +
NrPozycjaWartośćUwagi
01Wielkość grupydo 12Każdy pracuje na dokumentach swojego przedsiębiorstwa
02Czas trwania8–16 h1–2 dni, zależnie od ścieżki tematycznej
03FormaHybrydaStacjonarnie w Bełchatowie lub online na platformie
04Dostęp do materiałówbezterminowoNagrania, wzory pism i arkusze na platformie kursowej
05Szkolenia zamknięteod 6 osóbProgram dopasowany do sytuacji zamawiającego
06CertyfikattakPo zaliczeniu testu sprawdzającego
+

Warunki przykładowe - do potwierdzenia przed publikacją.

+
+
+ + +
+ 03 · Katalog szkoleń +
+
+

Osiem ścieżek tematycznych

+

Ścieżki można przechodzić pojedynczo albo jako komplet. Kolejność jest przemyślana: od otoczenia prawnego, przez ekonomikę, zakupy i relację z odbiorcą, po systemy, którymi to wszystko obsługujecie - a na końcu cyfryzacja, która spina jedno z drugim.

+
+ +
+ +
+ + Dokument taryfowy, paragraf i pieczęć zatwierdzenia +
+
Ścieżka 01Prawo i taryfyŚredni
+

Prawo wodne i wniosek taryfowy

+

Najczęściej zamawiane szkolenie w naszym katalogu. Zmiany w Prawie wodnym i ich skutki operacyjne, kalkulacja niezbędnych przychodów, budowa i uzasadnienie wniosku taryfowego, przebieg postępowania przed Wodami Polskimi, odpowiedzi na wezwania do uzupełnień, sprawozdawczość i obowiązki w zakresie jakości wody.

+
+ 2 dni · 16 hBełchatów / onlinePoziom: średni +
+
+ 1 400 PLNnetto / os. + Zapytaj o termin +
+
+
+ +
+ + Wskaźniki finansowe i struktura kosztów przedsiębiorstwa +
+
Ścieżka 02ZarządzanieŚredni
+

Zarządzanie przedsiębiorstwem wod-kan

+

Dla kadry kierowniczej i osób przygotowujących materiały dla zarządu. Rachunek kosztów i realna kalkulacja ceny m³, wskaźniki efektywności i benchmarking, wieloletni plan rozwoju i modernizacji, uzasadnianie inwestycji przed radą nadzorczą i właścicielem samorządowym, komunikacja podwyżek taryf.

+
+ 2 dni · 16 hBełchatów / onlinePoziom: średni +
+
+ 1 600 PLNnetto / os. + Zapytaj o termin +
+
+
+ +
+ + Porównanie ofert i kryteria oceny w postępowaniu +
+
Ścieżka 03ZamówieniaZaawansowany
+

Zamówienia publiczne w praktyce wod-kan

+

Warsztat na realnych dokumentach postępowań. Opis przedmiotu zamówienia, który nie kończy się aneksem, kryteria pozacenowe i koszt cyklu życia zamiast najniższej ceny, warunki udziału proporcjonalne do zamówienia, ocena i odrzucanie ofert, najczęstsze zarzuty odwoławcze i sposoby ich uniknięcia.

+
+ 2 dni · 16 hBełchatów / onlinePoziom: zaawansowany +
+
+ 1 800 PLNnetto / os. + Zapytaj o termin +
+
+
+ +
+ + Zgłoszenie odbiorcy, rozliczenie i komunikacja +
+
Ścieżka 04Obsługa klientaPodstawowy
+

Obsługa odbiorcy, umowy i billing

+

Dla biura obsługi klienta i działu rozliczeń. Umowy z odbiorcami i ich egzekwowanie, reklamacje i trudne rozmowy przy ladzie i przez telefon, windykacja należności krok po kroku, rozliczenia przy odczytach zdalnych, standardy obsługi i komunikacja przy awariach oraz przerwach w dostawie.

+
+ 1 dzień · 8 hBełchatów / onlinePoziom: podstawowy +
+
+ 900 PLNnetto / os. + Zapytaj o termin +
+
+
+ +
+ + Pulpit dyspozytorski: obiekty sieci, trend pomiarowy i sygnalizacja alarmu +
+
Ścieżka 05SCADAŚredni
+

Zarządzanie i monitoring systemów SCADA

+

Dla dyspozytorów, automatyków i kierowników ruchu. Jak czytać pulpit i nie utonąć w alarmach: progi i histereza zamiast fałszywych zgłoszeń, priorytety zdarzeń, procedury dyżuru i eskalacji, archiwizacja i retencja pomiarów, wskaźniki dostępności obiektów, raporty dla zarządu budowane z tych samych danych, na których pracuje dyspozytor.

+
+ 2 dni · 16 hBełchatów / onlinePoziom: średni +
+
+ 1 800 PLNnetto / os. + Zapytaj o termin +
+
+
+ +
+ + Audyt bezpieczeństwa: tarcza zgodności NIS2 i lista podatności z poziomami ryzyka +
+
Ścieżka 06BezpieczeństwoZaawansowany
+

Audyty bezpieczeństwa i zgodność NIS2

+

Sektor wodociągowy to infrastruktura krytyczna, więc dyrektywa NIS2 obowiązuje, a nie sugeruje. Inwentaryzacja systemów, kont i dostępów, segmentacja sieci biurowej od technologicznej, kopie zapasowe, które da się realnie odtworzyć, polityka haseł i uwierzytelnianie, plan reakcji na incydent, obowiązki zgłoszeniowe i dokumentacja na wypadek kontroli.

+
+ 2 dni · 16 hBełchatów / onlinePoziom: zaawansowany +
+
+ 2 200 PLNnetto / os. + Zapytaj o termin +
+
+
+ +
+ + Magistrala danych łącząca ERP, CRM, TAX i billing z raportowaniem oraz aplikacjami +
+
Ścieżka 07IntegracjeZaawansowany
+

Integracja z systemami klasy ERP, CRM i podatkowymi

+

Najczęstszy problem cyfryzacji nie brzmi „którego systemu użyć”, tylko „dlaczego te dane się nie zgadzają”. Które źródło jest nadrzędne dla której informacji, uzgadnianie sald i kartotek, integracja wsadowa a API, obsługa błędów i ponowień, wymagania integracyjne w opisie zamówienia oraz testy odbiorowe, które faktycznie wykryją rozjazd danych.

+
+ 2 dni · 16 hBełchatów / onlinePoziom: zaawansowany +
+
+ 2 200 PLNnetto / os. + Zapytaj o termin +
+
+
+ +
+ + Od papierowej dokumentacji do systemu centralnego z modułami i etapowym planem wdrożenia +
+
Ścieżka 08CyfryzacjaŚredni
+

Cyfryzacja przedsiębiorstwa wod-kan

+

Ścieżka domykająca pozostałe - przekłada wiedzę z prawa, organizacji i systemów na jeden plan działania. Dla osób, które mają zaplanować przejście z papieru na system i obronić ten plan przed zarządem. Przegląd obiegu informacji, inwentaryzacja majątku, obieg zleceń i dokumentów, integracja billingu z odczytami, dobór dostawcy i pisanie wymagań, realny harmonogram wdrożenia rozłożony na etapy budżetowe.

+
+ 2 dni · 16 hBełchatów / onlinePoziom: średni +
+
+ 1 600 PLNnetto / os. + Zapytaj o termin +
+
+
+ +
+ + Marketplace: nadwyżki z magazynu trafiają jako bezpłatne ogłoszenia do producentów i wykonawców +
+
WarsztatRynekPodstawowy
+

Skuteczne zapytanie ofertowe i wybór wykonawcy

+

Warsztat prowadzony na realnych ogłoszeniach z Giełdy Komunalnej. Jak opisać potrzebę, żeby dostać porównywalne oferty, jak czytać ofertę wykonawcy i wyłapać braki, gdzie szukać dostawcy w niszy, w której jest ich pięciu w kraju, jak upłynnić nadwyżki magazynowe zamiast spisywać je na straty.

+
+ 1 dzień · 8 hOnlinePoziom: podstawowy +
+
+ 900 PLNnetto / os. + Zapytaj o termin +
+
+
+ +
+

Ceny przykładowe - do potwierdzenia przed publikacją. Dla zespołów od 3 osób oraz dla uczestników kolejnych ścieżek przewidziane są rabaty.

+
+ + +
+ 04 · Harmonogram +
+
+

Terminy najbliższej edycji ustalamy

+

Poniżej tematy, które wchodzą do programu jako pierwsze. Daty podamy, gdy domkniemy salę i skład prowadzących - zwykle z sześciotygodniowym wyprzedzeniem. Zostaw kontakt przy interesującym Cię temacie, a odezwiemy się z terminem, zanim ogłosimy go publicznie.

+
+
+ +
+ Tematy w przygotowaniu + SPM-2026 + Rev A + Arkusz 05 z 06 +
+ + + + + + + + + + + + + + + + + +
TerminTematFormaMiejsceZapisy
WkrótceWniosek taryfowy - przygotowanie i obronaSzkolenieBełchatówPowiadom mnie →
WkrótceZmiany w Prawie wodnym - co dotyczy przedsiębiorstwWebinarOnlinePowiadom mnie →
WkrótceSkuteczne zapytanie ofertowe - warsztatSzkolenieOnlinePowiadom mnie →
WkrótceKryteria pozacenowe w postępowaniuWebinarOnlinePowiadom mnie →
WkrótceZarządzanie przedsiębiorstwem - wskaźniki i planSzkolenieBełchatówPowiadom mnie →
WkrótceCyfryzacja przedsiębiorstwa wod-kanSzkolenieOnlinePowiadom mnie →
WkrótceTaryfy - kalkulacja i najczęstsze błędyWebinarOnlinePowiadom mnie →
WkrótceWindykacja i trudny odbiorca - warsztatSzkolenieBełchatówPowiadom mnie →
WkrótceSprawozdawczość roczna - checklistaWebinarOnlinePowiadom mnie →
WkrótceAudyt bezpieczeństwa systemów - od czego zacząćSzkolenieBełchatówPowiadom mnie →
WkrótceNIS2 i infrastruktura krytyczna - obowiązki przedsiębiorstwaWebinarOnlinePowiadom mnie →
+

Ustalamy terminy najbliższej edycji. Zostaw kontakt przy wybranym temacie - poinformujemy Cię, gdy tylko otworzymy zapisy.

+
+
+ + +
+ 05 · Webinaria +
+
+
+

Pakiet roczny webinariów

+

Dwadzieścia cztery spotkania w roku - dwa w miesiącu, po dwie godziny. Jedno o przepisach i taryfach, jedno o organizacji i rynku. Jedna opłata dla całego przedsiębiorstwa, bez limitu uczestników.

+
    +
  • 24 webinaria rocznie na żywo, z sesją pytań do prowadzącego
  • +
  • Nagrania i materiały dostępne bezterminowo na platformie kursowej
  • +
  • Bez limitu uczestników z jednego przedsiębiorstwa
  • +
  • Archiwum wcześniejszych spotkań w cenie pakietu
  • +
  • Możliwość zgłoszenia tematu do programu na kolejny kwartał
  • +
  • Powiadomienia o zmianach w przepisach między spotkaniami
  • +
+
+ 4 800 PLNnetto / rok / przedsiębiorstwo + Zamów pakiet +
+
+
+ + Webinarium na żywo: okno prowadzącego, udostępniony slajd i panel pytań uczestników +
Rys. 03 - Webinarium na żywo z sesją pytań uczestników
+
+
+
+ + +
+ 06 · Zgłoszenie +
+
+ +

Zapytaj o termin lub szkolenie zamknięte

+

Zapisy na najbliższą edycję jeszcze nie są otwarte - zostaw kontakt, a odezwiemy się z terminem, zanim ogłosimy go publicznie. Odpowiadamy w ciągu jednego dnia roboczego. Jeśli szukasz programu pod konkretną sytuację, napisz, z czym macie problem; przygotujemy propozycję zakresu i wycenę.

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+ + +
+ Potwierdź, że nie jesteś robotem +
+ + Przesuń, aby zweryfikować + + + + +
+
+ +

+

Zapytania możesz też wysłać bezpośrednio na info@softspm.com.

+ +
+
+
+ +
+ + + + + +