Compare commits

..

31 Commits

Author SHA1 Message Date
Damir Radulic 2e022a7dcc fix(URGENT): SPA fallback serves sport2.html + 9 routers __future__ position
BUGS FIXED:
1. _serve_spa_fallback() returned index.html instead of sport2.html
   → User clicked /analitika /sufinanciranje etc and got wrong UI (DABI title)
   → Should serve sport2.html (PGZ SPORT - Platforma) with Analiza/Mreza/Link tabs

2. 9 router files had "from __future__" NOT at top of file
   → SyntaxError on import → routers SKIPPED → intermittent API failures
   → Affected: ocr.py, ocr_router.py, putni_nalozi.py, obrasci_router.py,
     clan_panel_router.py, audit_seal_router.py, erp_full_router.py,
     notif_router.py, seal.py

ROOT CAUSE:
Prior dehardcode batch (Master Zakon #1 sweep) inserted env-loading
imports BEFORE "from __future__ import annotations" — Python parser
requires __future__ FIRST.

FIX:
- _serve_spa_fallback() candidates list: sport2.html first
- Moved __future__ to top (preserving shebang + encoding + comments) in all 9

VERIFIED:
- 0 failed routers (was 7+)
- Analiza API: 10/10 success ~60-87ms
- Summary API: 5/5 success ~40ms
- sport.rinet.one/ → PGZ SPORT - Platforma (Analiza+Mreza tabs)
- All 9 SPA fallback routes serve sport2.html

Damir uploaded screenshot showing Analiza tab working (2,049 igraca,
82 klubova) but described as intermittent — root cause was router fails
causing some API endpoints to be missing/unreliable. Fixed.
2026-05-18 15:45:22 +02:00
Damir Radulic 386af1c5ed fix: 9 missing tab routes + SPA detail aliases (Playwright 0 errors)
ADDED:
- /klubovi/{id}, /savezi/{id}, /sportas/{id} detail aliases (Next.js RSC)
- 9 SPA fallback routes: /sufinanciranje, /trezor, /dashboard,
  /analitika, /pravila, /financiranje, /duplikati, /multifunkcija, /forensic
- All return FileResponse(sport.html) for client-side routing

VERIFIED: 14/14 routes 200, 0 console errors, 0 network failures
2026-05-18 15:02:50 +02:00
Damir Radulic aca5051418 feat: /api/v2/analiza/* endpoints - sport analytics backend 2026-05-16 00:28:12 +02:00
damir 7ca5d7d94e Sportaš kartica fix + hns_run.sh wrapper
CSS sport2.html:
- .player-card .ph img object-position: top → center 25% (face-aware crop)
- .ph aspect-ratio: 4/5 (portrait container, više prostora za sliku)

scripts/hns_run.sh:
- Wrapper koristi /usr/bin/python3 (ima psycopg2)
- Komande: master, deep, avatar, season, watchdog, objekti
- Damir-friendly (radi i u venv)
2026-05-05 18:39:01 +02:00
Damir Radulić bc59d1dc2d v2_router: remove legacy /erp/putni-nalozi stub
Stub referenced non-existent pgz_sport.putni_nalozi table and
shadowed the new /api/v2/erp/putni-nalozi (full CRUD on
expense_reports). Since v2_router is mounted before erp_full_router,
the stub won route resolution and returned the placeholder note.

Removed → new endpoint reachable, all 4 bug-rush tasks live-verified.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 18:37:37 +02:00
damir ae9c4e2bfd Sportski objekti: API + Leaflet map page + address enrichment
DB: pgz_sport.sportski_objekti (103 objekti, 103 s geo, 60 s adresom, 31 tip)

API:
- /api/v2/sportski-objekti (filter: tip, grad, sport, q)
- /api/v2/sportski-objekti/meta (tipovi, gradovi, sportovi, ukupno)

Frontend:
- /static/objekti.html — Leaflet (OpenStreetMap) interactive map
- 3 dropdown filter (tip, grad, sport) + search
- Side panel s listom + map markers s ikonama (🏟️🏊🎿🎳⛸️🎯🥌🏃)
- Popup: naziv, tip, kapacitet, adresa, upravitelj, izgradeno, sportovi, web link, Google Maps link
- /objekti, /sport/objekti, /sport/api/v2/sportski-objekti routes

Sidebar app.html: +Sportski objekti link
Background: scripts/objekti_enrich_address.py (Nominatim reverse-geocode 60 objekata bez adrese)
2026-05-05 18:35:04 +02:00
Damir Radulić 6e5ada8517 Merge agent3-payments: SEPA + CSV import + match workflow 2026-05-05 18:35:01 +02:00
Damir Radulić 47df057270 Merge agent2-putni: Putni nalozi CRUD + status workflow 2026-05-05 18:34:56 +02:00
Damir Radulić 7625e59173 Merge agent4-export: Universal Export ▾ CSV/XLSX/PDF 2026-05-05 18:34:51 +02:00
Damir Radulić c4640ca3af Merge agent1-ocr: OCR u ERP/CRM 2026-05-05 18:34:46 +02:00
Damir Radulić 38383d07c5 Task 4: Universal Export ▾ — CSV/XLSX/PDF dropdown across all screens
- routers/export_router.py: /api/v2/export?format=...&endpoint=...&filters=...
- static/js/export_dropdown.js: shared attachExportDropdown helper
- sport2/app/crm_v2/erp_full: Export ▾ button wired to representative tables
- pgz_sport_api.py: mount export_router with try/except

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 18:33:36 +02:00
Damir Radulić 9b0ed43b92 RUSH 4-sub: filteri Klubovi/Sportaši + manifestacije card view + CRM v2 redesign
RUSH-1 Klubovi: list_klubovi() LEFT JOIN v_klubovi_financiranje (prima_pgz/rss/grad_rijeka, u_godisnjaku, ukupno_potpora). financiran=true sad OR od 3 davatelja (drop legacy klubovi.pgz_sufinanciran s 1312 false-positive). Sort sort=potpora&order=desc. UI: gold ukupno_potpora + tooltip + sortable kolona. Defaults priority view (financirani+godišnjak ON, hns_roster OFF). Test: priority=604, +hns=36, all=1641, financiran=15 sorted ZAMET 80208€.

RUSH-2 Sportaši: SELECT widened (slika_url, reprezentativac, kategoriziran, broj_dresa). avatarUrl() helper s 3 forme (apsolutni / lokalni /sport/uploads/avatars / initials fallback) + 32px circular avatar lijevo od imena. Test: priority=3712, no-priority=6086, +hns=1439, 1990-2000=645.

RUSH-3 Manifestacije: bugfix razina filter HTTP 500 (ambiguous column nakon LEFT JOIN savezi → m.razina/mjesto/organizator). 3 dropdowna iz meta (26 mjesta / 8 razina / 50 organizatora), view toggle 🃏 Kartice / 📋 Tablica (localStorage), 🔗 link ikona u card+table, source_url → Google fallback. Test: default=3, mjesto=Lošinj=2, razina=Tradicionalna=3, organizator=AK Kvarner=1.

RUSH-4 CRM v2: tab strip rewrite (10 taba u spec redu Članarine|Liječnički|Obrasci|E-mail|Accounts|Contacts|Leads|Opps|Activities|Cases, sticky+scrollable+gold underline). Pipeline → Opps tab. Novi e-mail templates tab (5 endpointa, 3 seed templates, +Novi modal). Card layout (.cgrid/.ccard) za Accounts/Contacts/Leads/Opps. Export dropdown 📥 ▾ CSV/XLSX(SheetJS CDN)/PDF na svaki tab. Test: /crm_v2 200, 10/10 tab labela, 10 Export dropdowna + 31 exportTab() handlera.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 18:33:20 +02:00
Damir Radulić 55a27fb315 Task 3: Plaćanja — POST/PATCH + CSV batch import + SEPA XML mock
- routers/erp_full_router.py: POST/PATCH/import-csv/sepa-export
- static/erp_full.html: high-end UI s match workflow + SEPA export + summary tiles

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 18:29:51 +02:00
Damir Radulić efa15d0086 Task 2: Putni nalozi — full CRUD + status workflow
- routers/erp_full_router.py: GET/POST/PATCH/DELETE /api/v2/erp/putni-nalozi
  - status workflow: draft → poslano → odobreno/odbijeno → isplaceno
  - cost_total auto-calc, approved_at/paid_at on transitions
  - alias under /expense-reports/* preserved
- static/erp_full.html: novi UI lista + modal + status buttons

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 18:28:53 +02:00
Damir Radulić f488623920 Task 1: OCR u ERP/CRM — /api/ocr/upload + tab Računi (OCR)
- routers/ocr_router.py: POST /api/ocr/upload (Tesseract+pdf2image, regex field extraction)
- pgz_sport_api.py: mount ocr_router with try/except guard
- static/erp_full.html: nova tab "📷 OCR" + panel
- static/crm_v2.html: OCR upload modal/tab

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 18:28:22 +02:00
damir b72d037141 CRITICAL FIX (Slika 11, 12): /api/v2/auth/me alias + frontend fix
Bug: crm_v2.html, admin_users.html, ostali pozivali /api/v2/auth/me
koji ne postoji u backendu (postoji /api/auth/me bez v2).
401 redirect na /login?reason=unauthorized iako Damir prijavljen.

Fix:
- Frontend: replace /api/v2/auth/me → /api/auth/me u svim file-ovima
- Backend: dodan defensive alias @app.get('/api/v2/auth/me')
2026-05-05 18:25:52 +02:00
damir 8127e2ef22 Slika 3: Savez 3 KPI (ukupna potpora, sportaša, dokumenata)
Endpoint:
- /api/v2/savez/{id}/kpi (ukupna_potpora, broj_sportasa, broj_klubova, najvisi_rang, broj_dokumenata, broj_manifestacija)

Frontend sport2.html:
- loadSavezKpi() function
- Auto-call after openSavez(id) panel render
2026-05-05 18:24:05 +02:00
damir 7608839473 Auth fix: apiPost/apiPut/apiDelete uses Bearer token
sport2.html:
- apiPost: localStorage pgz_access → Authorization: Bearer
- apiPut, apiDelete added
- Better error toast

Login redirect (multiple files):
- Wrap auto-redirect in __pgz_made_api_call check
- Don't redirect on initial page load if user has no API call yet
2026-05-05 18:22:52 +02:00
damir 1bc30d7881 /sport/dokumenti UI podrzava i rows i dokumenti response key 2026-05-05 18:13:51 +02:00
damir 80ed621683 Frontend Financije: 4 dropdown (godina, davatelj, sport, vrsta) + listeners
sport2.html:
- loadFinancije: dynamic dropdown options from /v2/potpore/meta
- refreshFinancije: sport/vrsta/davatelj filter params
- 4 dropdown change listeners
2026-05-05 18:11:45 +02:00
damir a428363d42 V8 MEGA: meta endpoints + manifestacije + HNS V8 harvester batch
Endpoints:
- /v2/potpore/meta — dropdown options (sportovi, vrste, davatelji, godine)
- /v2/potpore/by-year — sport, vrsta filters
- /v2/manifestacije/meta — mjesta, razine, organizatori
- /v2/manifestacije — lista s filterima

HNS:
- 20 PGŽ priority klubova batch harvester pokrenut (HNK Goranin, HNK Orijent 1919, HNK Rijeka, NK Crikvenica, ...)
- ETA 30 min
2026-05-05 18:10:02 +02:00
damir f07fdad919 Crisis V7 MEGA: sufinanciranje_sport + panel + CRM auth
DB:
- pgz_sport.sufinanciranje_sport.je_klub flag (RSS programi/totals false)
- pgz_sport.sufinanciranje_sport.klub_id matched

Endpoints:
- /v2/potpore/by-year: samo_klubovi=True default + davatelj filter

Frontend:
- sport2.html PANEL FORCE HIDE CSS (right:-100vw default)
- crm_v2.html: redirect to /login only on actual 401, not on page load
2026-05-05 15:02:47 +02:00
damir 007825acee Bug hunt V7:
DB:
- Aggressive je_klub=false flag for programs/treninzi/totals (>100K€ no klub_id)
- 53 ne-klubovi flagged false (RSS Rijeka ukupni, Stručni rad, Potpora loptačkim, etc)

Frontend (sport2.html):
- Panel back button (← Natrag) + history stack
- window._panelHistory + pushPanelState + panelBack functions
- closePanel resets history
2026-05-05 14:56:53 +02:00
Damir Radulić 1e611d59f1 HNS sprint: 3-tab drill-down + parallel deep scraper dispatch
HNS-1 verify: smoke test 93409 OK, gap 854 uncovered, throughput ~60/min
HNS-2 dispatch: scripts/hns_dispatch.sh + 5 parallel workers shard'd po roster ID; coverage 265→1098 distinct_seasons (93.7% of 1172 roster), 125→971 distinct_matches; total seasons 3170→13371, matches 23515→150071
HNS-3 UI: 6-tab panel collapsed na 3 (🏆 Karijera / 📅 Utakmice / 👤 Profil); novi /api/v2/clan/{id}/hns-matches?limit=30 + /clan/{id}/hns-profile (bio + summary + HNS deep link); prof-grid 3-col card s gold jersey badge; OIB RBAC-mask. Test Josip Zec 449: 72 sez/30 utak.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 14:24:05 +02:00
damir 448273945c /sport/* aliases u app: admin, dokumenti, crm/v2, erp/full 2026-05-05 14:13:32 +02:00
damir 360b8008ba Crisis V6: panel expand + klub matching + ne-klub filter + samo_klubovi default
DB:
- pgz_sport.potpore_nositelji.je_klub flag (false za RSS programs/savezi)
- Re-match klub_id case-insensitive trim normalize

Endpoint:
- /api/dashboard/top-primatelji: samo_klubovi=True default

Frontend:
- sport2.html #panel/#dpanel: 70vw / 1100px max-width za HNS karijera
- mobile responsive za panel
2026-05-05 14:09:47 +02:00
Damir Radulić ce544e660c 7-sub sprint UI residual: footer login + kalendar CRUD + notif center + CRM extra tabs
A: shared/sidebar.js footer onclick → handleFootClick (Guest→/sport/login, logged-in→logout()), a11y role+keyboard, popup link fix
B: app.html SECTIONS['kalendar'] kalOpenModal/Save/Edit/Delete + Akcije kolona, mock savez:kalendar maknut
C: app.html renderNotifCenter() (sve 4 role) + sidebar bell unread badge (30s poll)
F: crm_v2.html +443 linija — Članarine, Liječnički, Obrasci tabovi (split view + dynamic schema modal)
G: index.html minor + sidebar dokumenti link refresh

Note: backend (kalendar_router, notif_router, crm_router, erp_full_router uploads, dokumenti unified) već u commit f7b5114.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 13:55:33 +02:00
damir f7b5114f58 PDF link target=_blank + nginx timeouts + priority filteri (samo s podacima)
nginx (sport.rinet.one):
- proxy_read_timeout 60s → 300s
- proxy_send_timeout 300s
- proxy_buffering off (PDF stream)
- client_max_body_size 50M → 100M

Endpoints:
- /api/v2/klubovi/financirani: +with_data filter (samo s potporama/godišnjakom/HNS)
- /api/v2/sportasi/filtered: +samo_priority +samo_s_hns

Frontend:
- PDF link target=_blank rel=noopener
- window._klub_only_priority = true (default)
- window._sportas_only_priority = true (default)

DB View:
- pgz_sport.v_nogomet_priority (prima_potpore, u_godisnjaku, ima_hns_roster)
2026-05-05 13:51:07 +02:00
damir c6a5ec62aa Dashboard UI: davatelj dropdown + dynamic years + KORISNIK truncate + PDF link 2026-05-05 13:43:30 +02:00
Damir Radulić 16b980e842 6-sub sprint: Dokumenti+HNS profil+Admin+ERP+CRM+PGŽ filter
SUB1 Dokumenti: pgz:dokumenti SECTIONS handler u app.html (klikabilan grid 19 godišnjaka, PDF stream)
SUB2 HNS profil: sport2.html drill-down — bio-chips (visina/težina/noga/poz/dres) + HNS deep + Google + Wiki + 🏆 Karijera/📅 Utakmice tabovi (Josip Zec id=449: 257 nast/182 gol/15 sez)
SUB3 Admin Users: sidebar.js href fix /admin/users → /sport/admin/users + razriješen audit ID konflikt
SUB4 ERP Full: 5 novih endpointa (invoice-uploads, racuni/ulazni/{rid}/uploads, expense-reports, putni-nalog-racuni, payments) + 3 nova taba (📎 Uploads/OCR, ✈ Putni, 💰 Plaćanja) + inline stavke drill-down + sidebar entry
SUB5 CRM Salesforce-Lite: dodan crm_v2 sidebar entry (router 956 linija već mounted)
SUB6 PGŽ filter: 2 nova endpointa /api/v2/savezi/priority-sort + /api/v2/clanovi/priority-sort; togglePGZFilter wired u Klubovi/Savezi/Sportaši (sport2.html + app.html); 💰📖 badge prefix; klubovi 1536/1641, savezi 35/246, sportaši 4979/5499

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 13:17:56 +02:00
damir 1d02c0897d Sidebar: +ERP +CRM +Dokumenti, godišnjaci import (18 PDFs), filter helpers
- pgz nav now includes /erp/full, /crm/v2, /admin/users, /dokumenti
- 4 dokumenti endpoints: list, godišnjaci/list, godišnjak/{godina} PDF, detail
- 18 godišnjaka u pgz_sport.dokumenti (2006-2024) with savez_id=333
- PGŽ filter helpers (window._pgz_filter_priority, togglePGZFilter)
- navItemClick handler for nav items with href
2026-05-05 13:08:11 +02:00
2487 changed files with 654114 additions and 1116 deletions
-1
View File
@@ -1 +0,0 @@
{"sessionId":"3eea00ef-fccd-4683-85c6-f7d39e8199a7","pid":1940465,"procStart":"327348495","acquiredAt":1777964592489}
Submodule .claude/worktrees/agent-a2230c7d02a7c02f4 added at f488623920
Submodule .claude/worktrees/agent-a54ff6ad4250d2734 added at 38383d07c5
Submodule .claude/worktrees/agent-a70769f0db14302aa added at 55a27fb315
Submodule .claude/worktrees/agent-af39fdf2dbfd08afe added at efa15d0086
-72
View File
@@ -1,72 +0,0 @@
# ═══════════════════════════════════════════════════════════════
# Ri.NET CENTRAL ENVIRONMENT
# Author: Damir Radulić | Updated: 25.04.2026
# Source: /opt/MASTER_CREDENTIALS_v5.md
# Permissions: 600 root:root
# ═══════════════════════════════════════════════════════════════
# === SERVERS ===
GPU_HOST=144.76.68.5
GPU_SSH_PORT=22
GPU_SSH_PASS='Gnu?CfR9hDBaER'
# === BRIDGE API ===
BRIDGE_URL=https://api.rinet.one/bridge/exec
BRIDGE_KEY=rinet-yS4ZnKlwUqsjk
# === DATABASE (PostgreSQL 18 on GPU) ===
PG_HOST=10.10.0.2
PG_PORT=6432
PG_DB=rinet_v3
PG_USER=rinet
PG_PASS='R1net2026!SecureDB#v7'
PG_PASS_POSTGRES='5852Dan1TR5852'
PG_BOUNCER_PORT=6432
DATABASE_URL=postgresql://rinet:R1net2026!SecureDB#v7@10.10.0.2:6432/rinet_v3
# === REDIS ===
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASS='R1netRedis2026v3'
# === HETZNER (DNS automatski!) ===
HETZNER_API_KEY='y2vBcp6QzkEvljhM0ujoazrJuiR7pi4pmtjTV276xUYWWUBEEindz7ZGWqWgU5yT'
HETZNER_DNS_TOKEN='iU5C2R60M4DUSUuqsIrJaRi3W1Hru8Dc'
HETZNER_SERVER_ID=2957676
ZONE_RINET_ONE=1005289
ZONE_RINET_DEV=1005291
ZONE_DABI_DIGITAL=1005292
# === CONTABO (legacy, samo WP) ===
CONTABO_CLIENT_ID=INT-12360074
CONTABO_SECRET=YAH3hF0BSdkf72hgH6vVjzdgrEWMTJZA
CONTABO_USER=dradulic@outlook.com
CONTABO_PASS='5852D@n1TR'
# === LLM API KEYS ===
GROQ_API_KEY=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
GEMINI_API_KEY=AIzaSyBHup6cmr8VkDm0l4uwBj5xRvuGA0W7XhI
DEEPSEEK_API_KEY=sk-33d29054d1ab4377b7d1a84bc0a423c7
OLLAMA_URL=http://localhost:11434
# === ADMIN/AUTH ===
ADMIN_EMAIL=admin@rinet.one
ADMIN_PASS='R1net2026!Admin'
GRAFANA_USER=admin
GRAFANA_PASS='R1net2026!Admin'
# === MAIL (Poste.io) ===
SMTP_HOST=mail.rinet.one
SMTP_PORT=587
SMTP_USER=admin@rinet.one
# === PGZ SPORT ERP ===
PGZ_SPORT_PORT=8095
PGZ_SPORT_ADMIN_TOKEN=admin-pgz-2026
PGZ_SPORT_VIEWER_TOKEN=viewer-pgz-2026
# === RUNTIME ===
TZ=Europe/Zagreb
LANG=hr_HR.UTF-8
GITHUB_TOKEN=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
GITHUB_PAT=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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,51 @@
Loaded 18 godišnjaka
Active klubova: 1658
godišnjak 2006: 299 klubova mentioned
godišnjak 2007: 310 klubova mentioned
godišnjak 2008: 317 klubova mentioned
godišnjak 2009: 317 klubova mentioned
godišnjak 2010: 316 klubova mentioned
godišnjak 2011: 335 klubova mentioned
godišnjak 2012: 313 klubova mentioned
godišnjak 2013: 326 klubova mentioned
godišnjak 2014: 324 klubova mentioned
godišnjak 2015: 348 klubova mentioned
godišnjak 2017: 337 klubova mentioned
godišnjak 2018: 342 klubova mentioned
godišnjak 2019: 358 klubova mentioned
godišnjak 2020: 384 klubova mentioned
godišnjak 2021: 371 klubova mentioned
godišnjak 2022: 385 klubova mentioned
godišnjak 2023: 396 klubova mentioned
godišnjak 2024: 420 klubova mentioned
=== Klubovi sa mentions: 559 ===
Updated 559 klubova sa godinama pojavljivanja
=== TOP 20 klubova po godinama pojavljivanja ===
18× Lučki radnik
18× NK Mrkopalj
18× NK Naprijed (H)
18× BK Sloga
18× Košarkaški klub ŠKRLJEVO
18× NK Turbina
18× NK Željezničar (M)
18× Nogometni klub GROBNIČAN
18× NK Primorac (Š)
18× Rukometni Klub Viškovo
18× Rukometni klub ZAMET
18× NK Snježnik
18× BK Kostrena
18× BK Studena
18× Kastav
18× Kostrena
18× Krenovac
18× Krimeja
18× Lovran
18× Krk
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/godisnjak_klub_mine.py", line 8, in <module>
user='rinet', password=os.environ["DB_PASSWORD"])
~~~~~~~~~~^^^^^^^^^^^^^^^
File "<frozen os>", line 685, in __getitem__
KeyError: 'DB_PASSWORD'
@@ -0,0 +1,57 @@
Loaded 18 godišnjaka
Indexed 6102 name variants for 3243 sportaša
godišnjak 2006: 45 matches
godišnjak 2007: 52 matches
godišnjak 2008: 75 matches
godišnjak 2009: 72 matches
godišnjak 2010: 77 matches
godišnjak 2011: 88 matches
godišnjak 2012: 108 matches
godišnjak 2013: 122 matches
godišnjak 2014: 153 matches
godišnjak 2015: 188 matches
godišnjak 2017: 277 matches
godišnjak 2018: 275 matches
godišnjak 2019: 268 matches
godišnjak 2020: 239 matches
godišnjak 2021: 259 matches
godišnjak 2022: 320 matches
godišnjak 2023: 367 matches
godišnjak 2024: 338 matches
Total sportaša mentioned: 989
Updated 989 sportaša
TOP 25 sportaša po godinama:
18× Ivan Peraić (nogomet)
18× Tonči Mikac (kuglanje KAT-1)
18× Velimir Liverić (?)
18× Velimir Liverić (svesportski KAT-2)
18× Miljenko Butković (svesportski KAT-1)
18× Ivan Mandekić (šah KAT-1)
17× Snježana Pejčić (streljaštvo KAT-1)
17× Miroslav Matić (boćanje)
17× Andrej Krstinić (streljaštvo KAT-1)
16× Krešimir Crnković (biatlon KAT-3)
16× Andrej Burić (odbojka)
16× Čedo Vukelić (boćanje)
16× Čedo Vukelić (boćanje)
15× Marko Strahija (plivanje)
15× Marko Skender (skijanje KAT-3)
15× Sara Pešut (svesportski KAT-1)
15× Slaviša Bradić (svesportski KAT-2)
15× Ela Znaor (kickbox KAT-1)
14× Spasoje Matijević (stolni tenis KAT-1)
14× Ognjen Cvitan (šah KAT-1)
14× Anika Kožica (biatlon KAT-3)
14× Vedran Dumenčić (parasport KAT-2)
14× Vedran Dumenčić (svesportski (slijepi) KAT-1)
14× Samir Barać (?)
14× Samir Barać (vaterpolo / svesportski KAT-1)
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/godisnjak_text_mine.py", line 8, in <module>
user='rinet', password=os.environ["DB_PASSWORD"])
~~~~~~~~~~^^^^^^^^^^^^^^^
File "<frozen os>", line 685, in __getitem__
KeyError: 'DB_PASSWORD'
File diff suppressed because it is too large Load Diff
+128
View File
@@ -0,0 +1,128 @@
savez_id (HBS): 2
=== I HBL 2025/2026 ===
natjecanje_id: 367 (8 klubova) PGZ=True
=== II HBL sjever 2025/2026 ===
natjecanje_id: 368 (12 klubova) PGZ=True
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hbs_lige_scraper.py", line 139, in <module>
cr.execute("""
psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "nat_tab_uniq"
DETAIL: Key (natjecanje_id, klub_naziv)=(368, Pazin) already exists.
savez_id (HBS): 2
=== I HBL 2025/2026 ===
natjecanje_id: 367 (8 klubova) PGZ=True
=== II HBL sjever 2025/2026 ===
natjecanje_id: 368 (12 klubova) PGZ=True
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hbs_lige_scraper.py", line 139, in <module>
cr.execute("""
psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "nat_tab_uniq"
DETAIL: Key (natjecanje_id, klub_naziv)=(368, Pazin) already exists.
savez_id (HBS): 2
=== I HBL 2025/2026 ===
natjecanje_id: 367 (8 klubova) PGZ=True
=== II HBL sjever 2025/2026 ===
natjecanje_id: 368 (12 klubova) PGZ=True
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hbs_lige_scraper.py", line 139, in <module>
cr.execute("""
psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "nat_tab_uniq"
DETAIL: Key (natjecanje_id, klub_naziv)=(368, Pazin) already exists.
savez_id (HBS): 2
=== I HBL 2025/2026 ===
natjecanje_id: 367 (8 klubova) PGZ=True
=== II HBL sjever 2025/2026 ===
natjecanje_id: 368 (12 klubova) PGZ=True
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hbs_lige_scraper.py", line 139, in <module>
cr.execute("""
psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "nat_tab_uniq"
DETAIL: Key (natjecanje_id, klub_naziv)=(368, Pazin) already exists.
savez_id (HBS): 2
=== I HBL 2025/2026 ===
natjecanje_id: 367 (8 klubova) PGZ=True
=== II HBL sjever 2025/2026 ===
natjecanje_id: 368 (12 klubova) PGZ=True
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hbs_lige_scraper.py", line 139, in <module>
cr.execute("""
psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "nat_tab_uniq"
DETAIL: Key (natjecanje_id, klub_naziv)=(368, Pazin) already exists.
savez_id (HBS): 2
=== I HBL 2025/2026 ===
natjecanje_id: 367 (8 klubova) PGZ=True
=== II HBL sjever 2025/2026 ===
natjecanje_id: 368 (12 klubova) PGZ=True
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hbs_lige_scraper.py", line 139, in <module>
cr.execute("""
psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "nat_tab_uniq"
DETAIL: Key (natjecanje_id, klub_naziv)=(368, Pazin) already exists.
savez_id (HBS): 2
=== I HBL 2025/2026 ===
natjecanje_id: 367 (8 klubova) PGZ=True
=== II HBL sjever 2025/2026 ===
natjecanje_id: 368 (12 klubova) PGZ=True
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hbs_lige_scraper.py", line 139, in <module>
cr.execute("""
psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "nat_tab_uniq"
DETAIL: Key (natjecanje_id, klub_naziv)=(368, Pazin) already exists.
savez_id (HBS): 2
=== I HBL 2025/2026 ===
natjecanje_id: 367 (8 klubova) PGZ=True
=== II HBL sjever 2025/2026 ===
natjecanje_id: 368 (12 klubova) PGZ=True
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hbs_lige_scraper.py", line 139, in <module>
cr.execute("""
psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "nat_tab_uniq"
DETAIL: Key (natjecanje_id, klub_naziv)=(368, Pazin) already exists.
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hbs_lige_scraper.py", line 8, in <module>
user="rinet", password=os.environ["DB_PASSWORD"])
~~~~~~~~~~^^^^^^^^^^^^^^^
File "<frozen os>", line 685, in __getitem__
KeyError: 'DB_PASSWORD'
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hbs_lige_scraper.py", line 8, in <module>
user="rinet", password=os.environ["DB_PASSWORD"])
~~~~~~~~~~^^^^^^^^^^^^^^^
File "<frozen os>", line 685, in __getitem__
KeyError: 'DB_PASSWORD'
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hbs_lige_scraper.py", line 8, in <module>
user="rinet", password=os.environ["DB_PASSWORD"])
~~~~~~~~~~^^^^^^^^^^^^^^^
File "<frozen os>", line 685, in __getitem__
KeyError: 'DB_PASSWORD'
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hbs_lige_scraper.py", line 8, in <module>
user="rinet", password=os.environ["DB_PASSWORD"])
~~~~~~~~~~^^^^^^^^^^^^^^^
File "<frozen os>", line 685, in __getitem__
KeyError: 'DB_PASSWORD'
+56
View File
@@ -0,0 +1,56 @@
=== SuperSport HNL ===
10 rows parsed
matched klub_id: 3/10
(4, 'Rijeka', 43)
=== SuperSport HNL ===
10 rows parsed
matched klub_id: 3/10
(4, 'Rijeka', 43)
=== SuperSport HNL ===
10 rows parsed
matched klub_id: 3/10
(4, 'Rijeka', 46)
=== SuperSport HNL ===
10 rows parsed
matched klub_id: 3/10
(4, 'Rijeka', 46)
=== SuperSport HNL ===
10 rows parsed
matched klub_id: 3/10
(4, 'Rijeka', 46)
=== SuperSport HNL ===
10 rows parsed
matched klub_id: 3/10
(4, 'Rijeka', 46)
=== SuperSport HNL ===
10 rows parsed
matched klub_id: 3/10
(3, 'Rijeka', 49)
=== SuperSport HNL ===
10 rows parsed
matched klub_id: 3/10
(3, 'Rijeka', 49)
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hnl_scraper.py", line 7, in <module>
user="rinet", password=os.environ["DB_PASSWORD"])
~~~~~~~~~~^^^^^^^^^^^^^^^
File "<frozen os>", line 685, in __getitem__
KeyError: 'DB_PASSWORD'
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hnl_scraper.py", line 7, in <module>
user="rinet", password=os.environ["DB_PASSWORD"])
~~~~~~~~~~^^^^^^^^^^^^^^^
File "<frozen os>", line 685, in __getitem__
KeyError: 'DB_PASSWORD'
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hnl_scraper.py", line 7, in <module>
user="rinet", password=os.environ["DB_PASSWORD"])
~~~~~~~~~~^^^^^^^^^^^^^^^
File "<frozen os>", line 685, in __getitem__
KeyError: 'DB_PASSWORD'
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hnl_scraper.py", line 7, in <module>
user="rinet", password=os.environ["DB_PASSWORD"])
~~~~~~~~~~^^^^^^^^^^^^^^^
File "<frozen os>", line 685, in __getitem__
KeyError: 'DB_PASSWORD'
File diff suppressed because it is too large Load Diff
+64
View File
@@ -0,0 +1,64 @@
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hns_lige_standings.py", line 173, in <module>
asyncio.run(run())
^^^^^^^
NameError: name 'asyncio' is not defined. Did you forget to import 'asyncio'?
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hns_lige_standings.py", line 173, in <module>
asyncio.run(run())
^^^^^^^
NameError: name 'asyncio' is not defined. Did you forget to import 'asyncio'?
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hns_lige_standings.py", line 173, in <module>
asyncio.run(run())
^^^^^^^
NameError: name 'asyncio' is not defined. Did you forget to import 'asyncio'?
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hns_lige_standings.py", line 173, in <module>
asyncio.run(run())
^^^^^^^
NameError: name 'asyncio' is not defined. Did you forget to import 'asyncio'?
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hns_lige_standings.py", line 173, in <module>
asyncio.run(run())
^^^^^^^
NameError: name 'asyncio' is not defined. Did you forget to import 'asyncio'?
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hns_lige_standings.py", line 173, in <module>
asyncio.run(run())
^^^^^^^
NameError: name 'asyncio' is not defined. Did you forget to import 'asyncio'?
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hns_lige_standings.py", line 173, in <module>
asyncio.run(run())
^^^^^^^
NameError: name 'asyncio' is not defined. Did you forget to import 'asyncio'?
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hns_lige_standings.py", line 173, in <module>
asyncio.run(run())
^^^^^^^
NameError: name 'asyncio' is not defined. Did you forget to import 'asyncio'?
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hns_lige_standings.py", line 14, in <module>
user='rinet', password=os.environ["DB_PASSWORD"])
~~~~~~~~~~^^^^^^^^^^^^^^^
File "<frozen os>", line 685, in __getitem__
KeyError: 'DB_PASSWORD'
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hns_lige_standings.py", line 14, in <module>
user='rinet', password=os.environ["DB_PASSWORD"])
~~~~~~~~~~^^^^^^^^^^^^^^^
File "<frozen os>", line 685, in __getitem__
KeyError: 'DB_PASSWORD'
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hns_lige_standings.py", line 14, in <module>
user='rinet', password=os.environ["DB_PASSWORD"])
~~~~~~~~~~^^^^^^^^^^^^^^^
File "<frozen os>", line 685, in __getitem__
KeyError: 'DB_PASSWORD'
Traceback (most recent call last):
File "/opt/pgz-sport/scrapers/hns_lige_standings.py", line 14, in <module>
user='rinet', password=os.environ["DB_PASSWORD"])
~~~~~~~~~~^^^^^^^^^^^^^^^
File "<frozen os>", line 685, in __getitem__
KeyError: 'DB_PASSWORD'
+250
View File
@@ -0,0 +1,250 @@
Length: 645718
Tables: 12
=== Table titles ===
Table 1: Natjecanja
Table 2: Natjecanja
Table 3: Natjecanja
Table 4: Natjecanja
Table 5: Natjecanja
Table 6: Natjecanja
Table 7: Natjecanja
Table 8: Natjecanja
=== Supersport Superliga (M) 2025/26 (10 klubova) ===
1. HAOK MLADOST 36b 18p 0por
2. MOK MURSA - OSIJEK 30b 15p 3por
3. OK RIBOLA KAŠTELA 22b 11p 7por
=== Supersport Superliga (Ž) 2025/26 (10 klubova) ===
1. HAOK MLADOST 32b 16p 2por
2. OK NEBO 26b 13p 5por
3. ŽOK RIBOLA KAŠTELA 26b 13p 5por
=== Liga doigravanje (M) 2025/26 (3 klubova) ===
1. MOK GROBNIČAN 4b 2p 0por
2. OK ZRINSKI NUŠTAR II 0b 0p 1por
3. OK CROATIA 0b 0p 1por
=== Supersport Superliga 2 (M) 2025/26 (10 klubova) ===
1. HAOK MLADOST II 32b 16p 2por
2. OK GORICA 22b 11p 7por
3. OK SPLIT 20b 10p 8por
=== Supersport Superliga 2 (Ž) 2025/26 (4 klubova) ===
1. OK SPLIT 6b 3p 0por
2. OK PETRINJA 4b 2p 1por
3. ŽOK DRENOVA 2b 1p 2por
=== TOTAL: 37, PGŽ klubovi: {'MOK RIJEKA', 'MOK GROBNIČAN', 'ŽOK DRENOVA', 'MOK RIJEKA II'} ===
=== HOS lige ===
10 klubova (1 matched) Supersport Superliga (M) 2025/26
10 klubova (0 matched) Supersport Superliga (Ž) 2025/26
3 klubova (1 matched) Liga doigravanje (M) 2025/26
10 klubova (1 matched) Supersport Superliga 2 (M) 2025/26
4 klubova (1 matched) Supersport Superliga 2 (Ž) 2025/26
0 klubova (0 matched) Superliga
47 klubova (3 matched) 1. B liga
0 klubova (0 matched) Kup Hrvatske
10 klubova (1 matched) Superliga
8 klubova (1 matched) Odbojka na pijesku
47 klubova (3 matched) 1. B liga
19 klubova (2 matched) Mlađe dobne kategorije
4 klubova (1 matched) 1. liga
8 klubova (1 matched) Odbojka na pijesku
19 klubova (2 matched) Mlađe dobne kategorije
10 klubova (1 matched) 1. liga
47 klubova (3 matched) 1. B liga
4 klubova (0 matched) Odbojka na pijesku
47 klubova (3 matched) 1. B liga
30 klubova (4 matched) 3. liga
10 klubova (1 matched) 1. liga
4 klubova (0 matched) Odbojka na pijesku
3 klubova (1 matched) 1. liga
10 klubova (0 matched) Superliga
0 klubova (0 matched) Kup Hrvatske
52 klubova (4 matched) 2. liga
19 klubova (2 matched) 2. liga
52 klubova (4 matched) 2. liga
52 klubova (4 matched) 2. liga
47 klubova (3 matched) 1. B liga
0 klubova (0 matched) 3. liga
=== PGŽ klubovi u HOS ===
1. B liga 1. ŽOK DRENOVA 36b -> 4529 'ŽOK Drenova'
1. B liga 1. ŽOK DRENOVA 36b -> 4529 'ŽOK Drenova'
1. B liga 1. ŽOK DRENOVA 36b -> 4529 'ŽOK Drenova'
1. B liga 1. ŽOK DRENOVA 36b -> 4529 'ŽOK Drenova'
1. B liga 1. ŽOK DRENOVA 36b -> 4529 'ŽOK Drenova'
1. B liga 4. OK GROBNIČAN 28b -> 4528 'MOK Grobničan'
1. B liga 4. OK GROBNIČAN 28b -> 4528 'MOK Grobničan'
1. B liga 4. OK GROBNIČAN 28b -> 4528 'MOK Grobničan'
1. B liga 4. OK GROBNIČAN 28b -> 4528 'MOK Grobničan'
1. B liga 4. OK GROBNIČAN 28b -> 4528 'MOK Grobničan'
1. liga 1. HAOK RIJEKA 36b -> 2398 'HAOK Rijeka (ranije ŽOK Rijeka)'
1. liga 1. MOK GROBNIČAN 4b -> 4528 'MOK Grobničan'
1. liga 3. ŽOK DRENOVA 2b -> 4529 'ŽOK Drenova'
1. liga 4. MOK RIJEKA II 20b -> 4530 'MOK RIJEKA II'
2. liga 2. MOK RIJEKA III 12b -> 4532 'MOK RIJEKA III'
2. liga 3. MOK GROBNIČAN 8b -> 4528 'MOK Grobničan'
2. liga 5. OK KASTAV 1998 6b -> 4531 'OK KASTAV 1998'
2. liga 5. OK KASTAV 1998 6b -> 4531 'OK KASTAV 1998'
2. liga 5. OK KASTAV 1998 6b -> 4531 'OK KASTAV 1998'
3. liga 1. ŽOK DRENOVA 4b -> 4529 'ŽOK Drenova'
3. liga 2. OK GROBNIČAN 2b -> 4528 'MOK Grobničan'
3. liga 4. HAOK RIJEKA 0b -> 2398 'HAOK Rijeka (ranije ŽOK Rijeka)'
Liga doigravanje (M) 2025/26 1. MOK GROBNIČAN 4b -> 4528 'MOK Grobničan'
Mlađe dobne kategorije 1. MOK GROBNIČAN 4b -> 4528 'MOK Grobničan'
Mlađe dobne kategorije 1. MOK GROBNIČAN 4b -> 4528 'MOK Grobničan'
Mlađe dobne kategorije 4. MOK RIJEKA 4b -> 2467 'MOK Rijeka'
Mlađe dobne kategorije 4. MOK RIJEKA 4b -> 2467 'MOK Rijeka'
Superliga 8. MOK RIJEKA 12b -> 2467 'MOK Rijeka'
Supersport Superliga 2 (M) 202 4. MOK RIJEKA II 20b -> 4530 'MOK RIJEKA II'
Supersport Superliga 2 (Ž) 202 3. ŽOK DRENOVA 2b -> 4529 'ŽOK Drenova'
Supersport Superliga (M) 2025/ 8. MOK RIJEKA 12b -> 4530 'MOK RIJEKA II'
Length: 638991
Tables: 12
=== Table titles ===
Table 1: Natjecanja
Table 2: Natjecanja
Table 3: Natjecanja
Table 4: Natjecanja
Table 5: Natjecanja
Table 6: Natjecanja
Table 7: Natjecanja
Table 8: Natjecanja
=== Supersport Superliga (M) 2025/26 (10 klubova) ===
1. HAOK MLADOST 36b 18p 0por
2. MOK MURSA - OSIJEK 30b 15p 3por
3. OK RIBOLA KAŠTELA 22b 11p 7por
=== Supersport Superliga (Ž) 2025/26 (10 klubova) ===
1. HAOK MLADOST 32b 16p 2por
2. OK NEBO 26b 13p 5por
3. ŽOK RIBOLA KAŠTELA 26b 13p 5por
=== Liga doigravanje (M) 2025/26 (10 klubova) ===
1. HAOK MLADOST II 32b 16p 2por
2. OK GORICA 22b 11p 7por
3. OK SPLIT 20b 10p 8por
=== Supersport Superliga 2 (M) 2025/26 (3 klubova) ===
1. MOK GROBNIČAN 4b 2p 0por
2. OK CROATIA 2b 1p 1por
3. OK ZRINSKI NUŠTAR II 0b 0p 2por
=== Supersport Superliga 2 (Ž) 2025/26 (4 klubova) ===
1. OK SPLIT 6b 3p 0por
2. OK PETRINJA 4b 2p 1por
3. ŽOK DRENOVA 2b 1p 2por
=== TOTAL: 37, PGŽ klubovi: {'MOK RIJEKA II', 'MOK GROBNIČAN', 'ŽOK DRENOVA', 'MOK RIJEKA'} ===
=== HOS lige ===
10 klubova (1 matched) Supersport Superliga (M) 2025/26
10 klubova (0 matched) Supersport Superliga (Ž) 2025/26
10 klubova (1 matched) Liga doigravanje (M) 2025/26
3 klubova (1 matched) Supersport Superliga 2 (M) 2025/26
4 klubova (1 matched) Supersport Superliga 2 (Ž) 2025/26
0 klubova (0 matched) Superliga
47 klubova (3 matched) 1. B liga
0 klubova (0 matched) Kup Hrvatske
10 klubova (1 matched) Superliga
8 klubova (1 matched) Odbojka na pijesku
47 klubova (3 matched) 1. B liga
19 klubova (2 matched) Mlađe dobne kategorije
4 klubova (1 matched) 1. liga
8 klubova (1 matched) Odbojka na pijesku
19 klubova (2 matched) Mlađe dobne kategorije
10 klubova (1 matched) 1. liga
47 klubova (3 matched) 1. B liga
4 klubova (0 matched) Odbojka na pijesku
47 klubova (3 matched) 1. B liga
30 klubova (4 matched) 3. liga
10 klubova (1 matched) 1. liga
4 klubova (0 matched) Odbojka na pijesku
3 klubova (1 matched) 1. liga
10 klubova (0 matched) Superliga
0 klubova (0 matched) Kup Hrvatske
52 klubova (4 matched) 2. liga
19 klubova (2 matched) 2. liga
52 klubova (4 matched) 2. liga
52 klubova (4 matched) 2. liga
47 klubova (3 matched) 1. B liga
0 klubova (0 matched) 3. liga
=== PGŽ klubovi u HOS ===
1. B liga 1. ŽOK DRENOVA 36b -> 4529 'ŽOK Drenova'
1. B liga 1. ŽOK DRENOVA 36b -> 4529 'ŽOK Drenova'
1. B liga 1. ŽOK DRENOVA 36b -> 4529 'ŽOK Drenova'
1. B liga 1. ŽOK DRENOVA 36b -> 4529 'ŽOK Drenova'
1. B liga 1. ŽOK DRENOVA 36b -> 4529 'ŽOK Drenova'
1. B liga 4. OK GROBNIČAN 28b -> 4528 'MOK Grobničan'
1. B liga 4. OK GROBNIČAN 28b -> 4528 'MOK Grobničan'
1. B liga 4. OK GROBNIČAN 28b -> 4528 'MOK Grobničan'
1. B liga 4. OK GROBNIČAN 28b -> 4528 'MOK Grobničan'
1. B liga 4. OK GROBNIČAN 28b -> 4528 'MOK Grobničan'
1. liga 1. MOK GROBNIČAN 4b -> 4528 'MOK Grobničan'
1. liga 1. HAOK RIJEKA 36b -> 2398 'HAOK Rijeka (ranije ŽOK Rijeka)'
1. liga 3. ŽOK DRENOVA 2b -> 4529 'ŽOK Drenova'
1. liga 4. MOK RIJEKA II 20b -> 4530 'MOK RIJEKA II'
2. liga 2. MOK RIJEKA III 12b -> 4532 'MOK RIJEKA III'
2. liga 3. MOK GROBNIČAN 8b -> 4528 'MOK Grobničan'
2. liga 5. OK KASTAV 1998 6b -> 4531 'OK KASTAV 1998'
2. liga 5. OK KASTAV 1998 6b -> 4531 'OK KASTAV 1998'
2. liga 5. OK KASTAV 1998 6b -> 4531 'OK KASTAV 1998'
3. liga 1. ŽOK DRENOVA 4b -> 4529 'ŽOK Drenova'
3. liga 2. OK GROBNIČAN 2b -> 4528 'MOK Grobničan'
3. liga 4. HAOK RIJEKA 0b -> 2398 'HAOK Rijeka (ranije ŽOK Rijeka)'
Liga doigravanje (M) 2025/26 4. MOK RIJEKA II 20b -> 4530 'MOK RIJEKA II'
Mlađe dobne kategorije 1. MOK GROBNIČAN 4b -> 4528 'MOK Grobničan'
Mlađe dobne kategorije 1. MOK GROBNIČAN 4b -> 4528 'MOK Grobničan'
Mlađe dobne kategorije 4. MOK RIJEKA 4b -> 2467 'MOK Rijeka'
Mlađe dobne kategorije 4. MOK RIJEKA 4b -> 2467 'MOK Rijeka'
Superliga 8. MOK RIJEKA 12b -> 2467 'MOK Rijeka'
Supersport Superliga 2 (M) 202 1. MOK GROBNIČAN 4b -> 4528 'MOK Grobničan'
Supersport Superliga 2 (Ž) 202 3. ŽOK DRENOVA 2b -> 4529 'ŽOK Drenova'
Supersport Superliga (M) 2025/ 8. MOK RIJEKA 12b -> 4530 'MOK RIJEKA II'
File "/opt/pgz-sport/scrapers/hos_scraper.py", line 8
DB = dict(host="10.10.0.2", port=6432, port=5432, dbname='rinet_v3', user='rinet', password='R1net2026!SecureDB#v7')
^^^^^^^^^
SyntaxError: keyword argument repeated: port
File "/opt/pgz-sport/scrapers/hos_scraper.py", line 8
DB = dict(host="10.10.0.2", port=6432, port=5432, dbname='rinet_v3', user='rinet', password='R1net2026!SecureDB#v7')
^^^^^^^^^
SyntaxError: keyword argument repeated: port
File "/opt/pgz-sport/scrapers/hos_scraper.py", line 8
DB = dict(host="10.10.0.2", port=6432, port=5432, dbname='rinet_v3', user='rinet', password='R1net2026!SecureDB#v7')
^^^^^^^^^
SyntaxError: keyword argument repeated: port
File "/opt/pgz-sport/scrapers/hos_scraper.py", line 8
DB = dict(host="10.10.0.2", port=6432, port=5432, dbname='rinet_v3', user='rinet', password='R1net2026!SecureDB#v7')
^^^^^^^^^
SyntaxError: keyword argument repeated: port
File "/opt/pgz-sport/scrapers/hos_scraper.py", line 8
DB = dict(host="10.10.0.2", port=6432, port=5432, dbname='rinet_v3', user='rinet', password='R1net2026!SecureDB#v7')
^^^^^^^^^
SyntaxError: keyword argument repeated: port
File "/opt/pgz-sport/scrapers/hos_scraper.py", line 8
DB = dict(host="10.10.0.2", port=6432, port=5432, dbname='rinet_v3', user='rinet', password='R1net2026!SecureDB#v7')
^^^^^^^^^
SyntaxError: keyword argument repeated: port
File "/opt/pgz-sport/scrapers/hos_scraper.py", line 9
DB = dict(host="10.10.0.2", port=6432, port=5432, dbname='rinet_v3', user='rinet', password=os.environ["DB_PASSWORD"])
^^^^^^^^^
SyntaxError: keyword argument repeated: port
File "/opt/pgz-sport/scrapers/hos_scraper.py", line 9
DB = dict(host="10.10.0.2", port=6432, port=5432, dbname='rinet_v3', user='rinet', password=os.environ["DB_PASSWORD"])
^^^^^^^^^
SyntaxError: keyword argument repeated: port
File "/opt/pgz-sport/scrapers/hos_scraper.py", line 9
DB = dict(host="10.10.0.2", port=6432, port=5432, dbname='rinet_v3', user='rinet', password=os.environ["DB_PASSWORD"])
^^^^^^^^^
SyntaxError: keyword argument repeated: port
File "/opt/pgz-sport/scrapers/hos_scraper.py", line 9
DB = dict(host="10.10.0.2", port=6432, port=5432, dbname='rinet_v3', user='rinet', password=os.environ["DB_PASSWORD"])
^^^^^^^^^
SyntaxError: keyword argument repeated: port
+6 -2
View File
@@ -1,4 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from dotenv import load_dotenv
load_dotenv('/opt/rinet-gpu/.env.master')
# auto-added by patch_scrapers_with_dotenv.sh
import os
# ═══════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════
# Fajl: admin_router.py | v1.1.0 | 04.05.2026 # Fajl: admin_router.py | v1.1.0 | 04.05.2026
# Autor: Damir Radulić <dradulic@outlook.com> # Autor: Damir Radulić <dradulic@outlook.com>
@@ -14,7 +18,7 @@ from datetime import datetime
import decimal, uuid import decimal, uuid
router = APIRouter(prefix="/admin/api", tags=["admin"]) router = APIRouter(prefix="/admin/api", tags=["admin"])
DSN = "host=10.10.0.2 port=6432 dbname=rinet_v3 user=rinet password=R1net2026!SecureDB#v7" DSN = f"host=10.10.0.2 port=6432 dbname=rinet_v3 user=rinet password={os.environ['DB_PASSWORD']}"
def db(): def db():
return psycopg2.connect(DSN, cursor_factory=RealDictCursor) return psycopg2.connect(DSN, cursor_factory=RealDictCursor)
@@ -324,7 +328,7 @@ import psycopg2 as _kpi_pg
async def admin_kpi(): async def admin_kpi():
"""Live KPI metrics — JSON za dashboard.""" """Live KPI metrics — JSON za dashboard."""
try: try:
conn = _kpi_pg.connect("host=10.10.0.2 port=6432 dbname=rinet_v3 user=rinet password=R1net2026!SecureDB#v7", connect_timeout=4) conn = _kpi_pg.connect(f"host=10.10.0.2 port=6432 dbname=rinet_v3 user=rinet password={os.environ['DB_PASSWORD']}", connect_timeout=4)
cur = conn.cursor() cur = conn.cursor()
out = {} out = {}
+402
View File
@@ -0,0 +1,402 @@
#!/usr/bin/env python3
import os
# ═══════════════════════════════════════════════════════════════════
# Fajl: admin_router.py | v1.1.0 | 04.05.2026
# Autor: Damir Radulić <dradulic@outlook.com>
# Lokacija: /opt/pgz-sport/admin_router.py
# Svrha: Admin Dashboard ERP+CRM+Tenants — pravo schema
# ═══════════════════════════════════════════════════════════════════
"""Admin dashboard backend."""
from fastapi import APIRouter, Query, HTTPException
from typing import Optional
import psycopg2
from psycopg2.extras import RealDictCursor
from datetime import datetime
import decimal, uuid
router = APIRouter(prefix="/admin/api", tags=["admin"])
DSN = f"host=10.10.0.2 port=6432 dbname=rinet_v3 user=rinet password={os.environ['DB_PASSWORD']}"
def db():
return psycopg2.connect(DSN, cursor_factory=RealDictCursor)
def conv(v):
if isinstance(v, datetime): return v.isoformat()
if isinstance(v, decimal.Decimal): return float(v)
if isinstance(v, uuid.UUID): return str(v)
return v
def jsonify(rows):
return [{k: conv(v) for k, v in dict(r).items()} for r in rows]
# ════════ DASHBOARD ════════
@router.get("/dashboard")
def dashboard(tenant_id: int = Query(1)):
with db() as conn, conn.cursor() as cur:
cur.execute("SELECT * FROM pgz_sport.tenants WHERE id = %s", (tenant_id,))
tenant = cur.fetchone()
if not tenant: raise HTTPException(404, "Tenant not found")
cur.execute("SELECT count(*) AS n FROM pgz_sport.klubovi WHERE tenant_id = %s", (tenant_id,))
klubovi = cur.fetchone()['n']
cur.execute("""
SELECT count(*) AS n FROM pgz_sport.klubovi k WHERE k.tenant_id = %s AND k.last_scraped_at > now() - interval '90 days'
""", (tenant_id,))
aktivni = cur.fetchone()['n']
cur.execute("SELECT count(*) AS n FROM pgz_sport.clanovi c JOIN pgz_sport.klubovi k ON k.id=c.klub_id WHERE k.tenant_id = %s", (tenant_id,))
osobe = cur.fetchone()['n']
cur.execute("""
SELECT count(*) AS n, COALESCE(SUM(amount_gross), 0) AS total_eur
FROM pgz_sport.invoices WHERE tenant_id = %s
""", (tenant_id,))
inv = cur.fetchone()
cur.execute("""
SELECT count(*) AS n, COALESCE(SUM(cost_total), 0) AS total_eur
FROM pgz_sport.expense_reports WHERE tenant_id = %s
""", (tenant_id,))
exp = cur.fetchone()
cur.execute("""
SELECT count(*) AS n FROM pgz_sport.audit_events
WHERE ts > now() - interval '30 days'
""")
activity = cur.fetchone()['n']
cur.execute("SELECT id, slug, display_name, type, status FROM pgz_sport.tenants ORDER BY id")
tenants = jsonify(cur.fetchall())
cur.execute("""
SELECT count(*) AS n FROM pgz_sport.dokumenti
WHERE scraped_at > now() - interval '7 days'
""")
docs_7d = cur.fetchone()['n']
return {
"tenant": jsonify([tenant])[0],
"kpi": {
"klubovi_total": klubovi,
"klubovi_aktivni_90d": aktivni,
"osobe": osobe,
"invoices": inv['n'],
"invoices_total_eur": float(inv['total_eur'] or 0),
"expenses": exp['n'],
"expenses_total_eur": float(exp['total_eur'] or 0),
"activity_30d": activity,
"dokumenti_7d": docs_7d
},
"tenants": tenants
}
# ════════ ERP ════════
@router.get("/erp/summary")
def erp_summary(tenant_id: int = Query(1)):
with db() as conn, conn.cursor() as cur:
cur.execute("""
SELECT count(*) AS total,
count(*) FILTER (WHERE payment_status = 'paid') AS paid,
count(*) FILTER (WHERE payment_status = 'pending') AS pending,
count(*) FILTER (WHERE payment_status = 'overdue') AS overdue,
count(*) FILTER (WHERE payment_status NOT IN ('paid','pending','overdue') OR payment_status IS NULL) AS other,
COALESCE(SUM(amount_gross), 0) AS sum_total,
COALESCE(SUM(amount_gross) FILTER (WHERE payment_status = 'paid'), 0) AS sum_paid,
COALESCE(SUM(amount_gross) FILTER (WHERE payment_status != 'paid' OR payment_status IS NULL), 0) AS sum_unpaid
FROM pgz_sport.invoices WHERE tenant_id = %s
""", (tenant_id,))
inv = cur.fetchone()
cur.execute("""
SELECT count(*) AS total, COALESCE(SUM(cost_total), 0) AS sum_total,
count(*) FILTER (WHERE status = 'approved') AS approved,
count(*) FILTER (WHERE status = 'paid') AS paid_status
FROM pgz_sport.expense_reports WHERE tenant_id = %s
""", (tenant_id,))
exp = cur.fetchone()
cur.execute("""
SELECT count(*) AS total, COALESCE(SUM(amount), 0) AS sum_total
FROM pgz_sport.payments p
JOIN pgz_sport.klubovi k ON k.id = p.klub_id
WHERE k.tenant_id = %s AND p.payment_date > now() - interval '90 days'
""", (tenant_id,))
pay = cur.fetchone()
cur.execute("""
SELECT count(*) AS n, COALESCE(SUM(proracun_pgz), 0) AS sum_planirano,
COALESCE(SUM(ukupno), 0) AS sum_izvrseno
FROM pgz_sport.proracun
""")
prc = cur.fetchone()
return {
"invoices": jsonify([inv])[0],
"expenses": jsonify([exp])[0],
"payments_90d": jsonify([pay])[0],
"proracun": jsonify([prc])[0]
}
@router.get("/erp/invoices")
def erp_invoices(tenant_id: int = Query(1), limit: int = Query(50), status: Optional[str] = None):
with db() as conn, conn.cursor() as cur:
sql = """
SELECT i.id, i.invoice_no, i.vendor_name, i.amount_gross, i.currency,
i.payment_status, i.invoice_date, i.due_date, i.paid_date,
i.klub_id, k.naziv AS klub_naziv
FROM pgz_sport.invoices i
LEFT JOIN pgz_sport.klubovi k ON k.id = i.klub_id
WHERE i.tenant_id = %s
"""
params = [tenant_id]
if status:
sql += " AND i.payment_status = %s"
params.append(status)
sql += " ORDER BY i.invoice_date DESC NULLS LAST LIMIT %s"
params.append(limit)
cur.execute(sql, params)
rows = jsonify(cur.fetchall())
return {"invoices": rows, "count": len(rows)}
@router.get("/erp/expenses")
def erp_expenses(tenant_id: int = Query(1), limit: int = Query(50)):
with db() as conn, conn.cursor() as cur:
cur.execute("""
SELECT e.id, e.klub_id, k.naziv AS klub_naziv, e.report_no,
e.destination, e.purpose, e.cost_total, e.dnevnice_amount,
e.date_from, e.date_to, e.status, e.created_at
FROM pgz_sport.expense_reports e
LEFT JOIN pgz_sport.klubovi k ON k.id = e.klub_id
WHERE e.tenant_id = %s
ORDER BY e.created_at DESC NULLS LAST LIMIT %s
""", (tenant_id, limit))
rows = jsonify(cur.fetchall())
return {"expenses": rows, "count": len(rows)}
# ════════ CRM ════════
@router.get("/crm/klubovi")
def crm_klubovi(tenant_id: int = Query(1), limit: int = Query(50), q: Optional[str] = None):
with db() as conn, conn.cursor() as cur:
sql = """
SELECT k.id, k.naziv, k.oib, k.adresa, k.grad, k.email, k.telefon, k.web,
k.sport, k.savez_id, k.aktivan,
0 AS dokumenti,
(SELECT count(*) FROM pgz_sport.invoices i WHERE i.klub_id = k.id) AS invoices_count,
(SELECT count(*) FROM pgz_sport.clanovi c WHERE c.klub_id = k.id) AS clanovi,
k.last_scraped_at AS last_activity
FROM pgz_sport.klubovi k
WHERE k.tenant_id = %s
"""
params = [tenant_id]
if q:
sql += " AND (k.naziv ILIKE %s OR k.oib LIKE %s OR k.grad ILIKE %s OR k.sport ILIKE %s)"
params.extend([f"%{q}%", f"%{q}%", f"%{q}%", f"%{q}%"])
sql += " ORDER BY k.naziv LIMIT %s"
params.append(limit)
cur.execute(sql, params)
rows = jsonify(cur.fetchall())
return {"klubovi": rows, "count": len(rows)}
@router.get("/crm/klub/{klub_id}")
def crm_klub_detail(klub_id: int):
with db() as conn, conn.cursor() as cur:
cur.execute("SELECT * FROM pgz_sport.klubovi WHERE id = %s", (klub_id,))
klub = cur.fetchone()
if not klub: raise HTTPException(404, "Klub not found")
cur.execute("""
SELECT id, title AS naziv, vrsta, sport AS kategorija, scraped_at AS created_at FROM pgz_sport.dokumenti WHERE FALSE LIMIT 20
""", (klub_id,))
dokumenti = jsonify(cur.fetchall())
cur.execute("SELECT count(*) AS n FROM pgz_sport.clanovi WHERE klub_id = %s", (klub_id,))
clanovi_n = cur.fetchone()['n']
cur.execute("""
SELECT id, invoice_no, vendor_name, amount_gross, payment_status, invoice_date
FROM pgz_sport.invoices WHERE klub_id = %s
ORDER BY invoice_date DESC LIMIT 10
""", (klub_id,))
invoices = jsonify(cur.fetchall())
cur.execute("""
SELECT id, report_no, destination, cost_total, status, created_at
FROM pgz_sport.expense_reports WHERE klub_id = %s
ORDER BY created_at DESC LIMIT 10
""", (klub_id,))
expenses = jsonify(cur.fetchall())
return {
"klub": jsonify([klub])[0],
"dokumenti": dokumenti,
"clanovi_count": clanovi_n,
"invoices": invoices,
"expenses": expenses
}
@router.get("/crm/osobe")
def crm_osobe(limit: int = Query(50), q: Optional[str] = None, klub_id: Optional[int] = None):
with db() as conn, conn.cursor() as cur:
sql = """
SELECT c.id, c.ime, c.prezime, c.oib, c.email, c.telefon, c.klub_id,
k.naziv AS klub_naziv, c.pozicija, c.kategorija, c.aktivan, c.datum_rodenja
FROM pgz_sport.clanovi c
LEFT JOIN pgz_sport.klubovi k ON k.id = c.klub_id
WHERE 1=1
"""
params = []
if q:
sql += " AND (c.ime ILIKE %s OR c.prezime ILIKE %s OR c.oib LIKE %s)"
params.extend([f"%{q}%", f"%{q}%", f"%{q}%"])
if klub_id:
sql += " AND c.klub_id = %s"
params.append(klub_id)
sql += " ORDER BY c.prezime, c.ime LIMIT %s"
params.append(limit)
cur.execute(sql, params)
rows = jsonify(cur.fetchall())
return {"osobe": rows, "count": len(rows)}
# ════════ TENANTS ════════
@router.get("/tenants")
def tenants_list():
with db() as conn, conn.cursor() as cur:
cur.execute("SELECT * FROM pgz_sport.tenants ORDER BY id")
rows = jsonify(cur.fetchall())
# Add live KPIs
for t in rows:
cur.execute("SELECT count(*) AS n FROM pgz_sport.klubovi WHERE tenant_id = %s", (t['id'],))
t['klubovi_count'] = cur.fetchone()['n']
return {"tenants": rows, "count": len(rows)}
@router.get("/tenants/{tenant_id}")
def tenant_detail(tenant_id: int):
with db() as conn, conn.cursor() as cur:
cur.execute("SELECT * FROM pgz_sport.tenants WHERE id = %s", (tenant_id,))
t = cur.fetchone()
if not t: raise HTTPException(404, "Not found")
return {"tenant": jsonify([t])[0]}
@router.post("/tenants")
def tenants_create(slug: str, display_name: str, oib: Optional[str] = None, type: str = "custom"):
with db() as conn:
conn.autocommit = True
with conn.cursor() as cur:
cur.execute("""
INSERT INTO pgz_sport.tenants (slug, display_name, oib, type)
VALUES (%s, %s, %s, %s) RETURNING id
""", (slug, display_name, oib, type))
new_id = cur.fetchone()['id']
return {"id": new_id, "slug": slug, "status": "created"}
# ════════ REPORTS ════════
@router.get("/reports/top_klubovi")
def reports_top_klubovi(tenant_id: int = Query(1), limit: int = Query(10)):
with db() as conn, conn.cursor() as cur:
cur.execute("""
SELECT k.id, k.naziv, k.grad, k.sport,
count(DISTINCT d.id) AS dokumenti,
count(DISTINCT i.id) AS invoices,
count(DISTINCT c.id) AS clanovi
FROM pgz_sport.klubovi k
LEFT JOIN pgz_sport.dokumenti d ON FALSE
LEFT JOIN pgz_sport.invoices i ON i.klub_id = k.id
LEFT JOIN pgz_sport.clanovi c ON c.klub_id = k.id
WHERE k.tenant_id = %s
GROUP BY k.id, k.naziv, k.grad, k.sport
ORDER BY (count(DISTINCT d.id) + count(DISTINCT i.id)) DESC
LIMIT %s
""", (tenant_id, limit))
rows = jsonify(cur.fetchall())
return {"top_klubovi": rows}
@router.get("/health")
def admin_health():
return {"status": "ok", "module": "admin", "version": "1.1.0", "ts": datetime.utcnow().isoformat()}
# ═══════════════════════════════════════════════════════════════════
# KPI Dashboard endpoint (added 04.05.2026 evening sprint)
# ═══════════════════════════════════════════════════════════════════
import psycopg2 as _kpi_pg
@router.get("/kpi")
async def admin_kpi():
"""Live KPI metrics — JSON za dashboard."""
try:
conn = _kpi_pg.connect(f"host=10.10.0.2 port=6432 dbname=rinet_v3 user=rinet password={os.environ['DB_PASSWORD']}", connect_timeout=4)
cur = conn.cursor()
out = {}
# Capture stats
cur.execute("""
SELECT
count(*) FILTER (WHERE created_at > now() - interval '1 hour') AS h1,
count(*) FILTER (WHERE created_at > now() - interval '24 hours') AS h24,
count(*) FILTER (WHERE created_at > now() - interval '24 hours' AND is_hallucination) AS halu24,
round((avg(processing_time) FILTER (WHERE created_at > now() - interval '24 hours'))::numeric, 1) AS avg_lat,
round((avg(confidence) FILTER (WHERE created_at > now() - interval '24 hours'))::numeric, 2) AS avg_conf
FROM dabi.input_log
""")
r = cur.fetchone()
out["queries"] = {"h1": r[0], "h24": r[1], "halucinacije_h24": r[2] or 0,
"avg_latency_sec": float(r[3]) if r[3] else 0, "avg_confidence": float(r[4]) if r[4] else 0,
"halu_pct": round(100*(r[2] or 0)/max(r[1],1), 2)}
# Knowledge
cur.execute("""
SELECT count(*),
count(*) FILTER (WHERE created_at > now() - interval '1 hour'),
count(*) FILTER (WHERE created_at > now() - interval '24 hours'),
count(*) FILTER (WHERE embedded_at IS NULL),
round(100.0 * count(*) FILTER (WHERE embedded_at IS NOT NULL) / count(*), 2)
FROM dabi.knowledge
""")
r = cur.fetchone()
out["knowledge"] = {"total": r[0], "added_h1": r[1], "added_h24": r[2],
"embed_pending": r[3], "embed_pct": float(r[4])}
# Cluster
cur.execute("SELECT health_status, count(*) FROM cluster.services GROUP BY health_status")
out["cluster"] = {row[0]: row[1] for row in cur.fetchall()}
cur.execute("SELECT count(*) FROM deploys.incidents WHERE resolved_at IS NULL")
out["open_incidents"] = cur.fetchone()[0]
# Training
cur.execute("""
SELECT count(*), count(*) FILTER (WHERE source_type='capture_promoted'),
count(*) FILTER (WHERE created_at > now() - interval '24 hours')
FROM dabi.training_qa
""")
r = cur.fetchone()
out["training"] = {"total": r[0], "from_capture": r[1], "added_h24": r[2]}
# Top sources last 24h
cur.execute("""
SELECT source, count(*) FROM dabi.knowledge
WHERE created_at > now() - interval '24 hours'
GROUP BY source ORDER BY 2 DESC LIMIT 10
""")
out["top_sources_h24"] = [{"source": s, "count": n} for s, n in cur.fetchall()]
# Top models last 24h
cur.execute("""
SELECT model_used, count(*), round(avg(processing_time)::numeric, 1)
FROM dabi.input_log WHERE created_at > now() - interval '24 hours'
GROUP BY model_used ORDER BY 2 DESC LIMIT 5
""")
out["top_models_h24"] = [{"model": m, "count": n, "avg_latency": float(l) if l else 0} for m, n, l in cur.fetchall()]
cur.close(); conn.close()
return out
except Exception as e:
return {"error": str(e)}
@router.get("/kpi-page", include_in_schema=False)
async def admin_kpi_html():
"""HTML KPI dashboard page."""
from fastapi.responses import FileResponse
return FileResponse("/opt/pgz-sport/static/kpi.html")
+28
View File
@@ -370,6 +370,34 @@ def admin_reset_password(uid: int, request: Request, actor = Depends(require_use
{"email": target["email"]}, ip, ua) {"email": target["email"]}, ip, ua)
return {"status": "ok", "temporary_password": new_temp} return {"status": "ok", "temporary_password": new_temp}
# ─────────────────────────── 2FA admin (status / force disable) ───────────────────────────
@router.get("/users/{uid}/2fa-status")
def admin_2fa_status(uid: int, actor = Depends(require_user)):
target = db_one("SELECT user_type, klub_id, savez_id FROM pgz_sport.users WHERE id=%s", (uid,))
if not target: raise HTTPException(404, "User not found")
if not _can_manage(actor, target["user_type"], target["klub_id"], target["savez_id"]):
raise HTTPException(403, "Forbidden")
row = db_one("""SELECT enabled, verified_at, created_at, updated_at
FROM pgz_sport.user_2fa WHERE user_id=%s""", (uid,))
return {"enabled": bool(row and row.get("enabled")),
"verified_at": row and row.get("verified_at"),
"created_at": row and row.get("created_at"),
"updated_at": row and row.get("updated_at")}
@router.post("/users/{uid}/2fa-disable")
def admin_2fa_disable(uid: int, request: Request, actor = Depends(require_user)):
target = db_one("SELECT user_type, klub_id, savez_id, email FROM pgz_sport.users WHERE id=%s",
(uid,))
if not target: raise HTTPException(404, "User not found")
if not _can_manage(actor, target["user_type"], target["klub_id"], target["savez_id"]):
raise HTTPException(403, "Forbidden")
db_exec("DELETE FROM pgz_sport.user_2fa WHERE user_id=%s", (uid,))
db_exec("UPDATE pgz_sport.user_sessions SET revoked=true WHERE user_id=%s", (uid,))
ip, ua = _client(request)
audit(actor["id"], "user.2fa.admin_disable", "user", uid,
{"email": target["email"]}, ip, ua)
return {"status": "ok", "id": uid, "two_factor_enabled": False}
# ─────────────────────────── Audit log ─────────────────────────── # ─────────────────────────── Audit log ───────────────────────────
@router.get("/audit") @router.get("/audit")
def audit_log(user_id: Optional[int] = None, def audit_log(user_id: Optional[int] = None,
+4 -1
View File
@@ -1,4 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from dotenv import load_dotenv
load_dotenv('/opt/rinet-gpu/.env.master')
# auto-added by patch_scrapers_with_dotenv.sh
# auth_v2.py — JWT auth backend with tenant_id, role, tier claims # auth_v2.py — JWT auth backend with tenant_id, role, tier claims
# v1.0 dradulic@outlook.com / damir@rinet.one — 2026-05-04 # v1.0 dradulic@outlook.com / damir@rinet.one — 2026-05-04
# Endpoints: /api/auth/login, /api/auth/refresh, /api/auth/logout, # Endpoints: /api/auth/login, /api/auth/refresh, /api/auth/logout,
@@ -33,7 +36,7 @@ except Exception:
HAS_BCRYPT = False HAS_BCRYPT = False
DB = dict(host='10.10.0.2', port=6432, dbname='rinet_v3', DB = dict(host='10.10.0.2', port=6432, dbname='rinet_v3',
user='rinet', password='R1net2026!SecureDB#v7') user='rinet', password=os.environ["DB_PASSWORD"])
# Persistent JWT secret — read from env, else stable file, else generated. # Persistent JWT secret — read from env, else stable file, else generated.
def _load_secret() -> str: def _load_secret() -> str:
+951
View File
@@ -0,0 +1,951 @@
#!/usr/bin/env python3
# auth_v2.py — JWT auth backend with tenant_id, role, tier claims
# v1.0 dradulic@outlook.com / damir@rinet.one — 2026-05-04
# Endpoints: /api/auth/login, /api/auth/refresh, /api/auth/logout,
# /api/auth/me, /api/auth/password/change, /api/auth/password/reset
"""
JWT claims:
sub int user id
email str
name str
tenant_id int|null pgz_sport.tenants.id (or null for super_admin)
tenant_type str pgz | savez | klub | global
tenant_scope dict {"klub_id": ..., "savez_id": ...}
role str user_type code (super_admin | pgz_admin | savez_admin | klub_admin | klub_clan | viewer ...)
tier int 0 = PGŽ, 1 = savez, 2 = klub
jti str token id (revocable via user_sessions)
iat / exp / nbf
"""
import os, hashlib, secrets, json, time
from datetime import datetime, timedelta, timezone
from typing import Optional, Dict, List, Any
import jwt as _jwt
import psycopg2, psycopg2.extras
from fastapi import APIRouter, HTTPException, Header, Depends, Request, Body
from pydantic import BaseModel, EmailStr
try:
from passlib.hash import bcrypt as _bcrypt
HAS_BCRYPT = True
except Exception:
HAS_BCRYPT = False
DB = dict(host='10.10.0.2', port=6432, dbname='rinet_v3',
user='rinet', password=os.environ["DB_PASSWORD"])
# Persistent JWT secret — read from env, else stable file, else generated.
def _load_secret() -> str:
env_secret = os.environ.get("PGZ_JWT_SECRET")
if env_secret and len(env_secret) >= 32:
return env_secret
secret_file = "/opt/pgz-sport/auth/.jwt_secret"
try:
if os.path.exists(secret_file):
with open(secret_file) as f:
s = f.read().strip()
if len(s) >= 32:
return s
s = "rinet-pgz-" + secrets.token_urlsafe(48)
with open(secret_file, "w") as f:
f.write(s)
os.chmod(secret_file, 0o600)
return s
except Exception:
return "rinet-pgz-jwt-2026-fallback-" + hashlib.sha256(b"pgz-sport").hexdigest()
JWT_SECRET = _load_secret()
JWT_ALG = "HS256"
ACCESS_TTL = timedelta(minutes=int(os.environ.get("PGZ_JWT_ACCESS_MIN", "30")))
REFRESH_TTL = timedelta(days=int(os.environ.get("PGZ_JWT_REFRESH_DAYS", "7")))
router = APIRouter(prefix="/api/auth", tags=["auth_v2"])
# ─────────────────────────── DB helpers ───────────────────────────
def _conn():
return psycopg2.connect(**DB)
def db_query(sql: str, params=()):
with _conn() as c:
cur = c.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
cur.execute(sql, params)
if cur.description: return cur.fetchall()
return []
def db_one(sql: str, params=()):
rows = db_query(sql, params)
return rows[0] if rows else None
def db_exec(sql: str, params=()):
with _conn() as c:
cur = c.cursor()
cur.execute(sql, params)
if cur.description:
r = cur.fetchone()
return r[0] if r else None
c.commit()
# ─────────────────────────── Password helpers ───────────────────────────
def _sha256(pw: str) -> str:
return hashlib.sha256(pw.encode()).hexdigest()
def hash_password(pw: str) -> str:
if HAS_BCRYPT:
return _bcrypt.using(rounds=12).hash(pw)
return _sha256(pw)
def verify_password(pw: str, hashed: Optional[str]) -> bool:
if not hashed: return False
h = hashed.strip()
if h.startswith("$2") and HAS_BCRYPT:
try:
return _bcrypt.verify(pw, h)
except Exception:
return False
return h == _sha256(pw)
def needs_rehash(hashed: Optional[str]) -> bool:
if not hashed: return True
return HAS_BCRYPT and not hashed.startswith("$2")
# ─────────────────────────── Tenant resolution ───────────────────────────
PGZ_USER_TYPES = {"super_admin", "pgz_admin", "pgz_user", "pgz_finance", "pgz_zzjz"}
SAVEZ_USER_TYPES = {"savez_admin", "savez_user"}
KLUB_USER_TYPES = {"klub_admin", "klub_user", "klub_trener", "klub_clan"}
def _tier_for(user_type: str) -> int:
ut = (user_type or "").lower()
if ut in PGZ_USER_TYPES: return 0
if ut in SAVEZ_USER_TYPES: return 1
if ut in KLUB_USER_TYPES: return 2
return 9 # unknown / viewer / guest
def _resolve_tenant(u: Dict) -> Dict:
"""Resolve tenant_id + tenant_type from a user row."""
ut = (u.get("user_type") or "").lower()
klub_id = u.get("klub_id")
savez_id = u.get("savez_id")
if ut in PGZ_USER_TYPES:
row = db_one("SELECT id, slug, display_name FROM pgz_sport.tenants WHERE slug='pgz' LIMIT 1")
return {
"tenant_id": row["id"] if row else None,
"tenant_type": "pgz",
"tenant_name": row["display_name"] if row else "PGŽ",
"tenant_scope": {"klub_id": None, "savez_id": None},
}
if ut in SAVEZ_USER_TYPES and savez_id:
return {
"tenant_id": savez_id,
"tenant_type": "savez",
"tenant_name": (db_one("SELECT naziv FROM pgz_sport.savezi WHERE id=%s",(savez_id,)) or {}).get("naziv"),
"tenant_scope": {"klub_id": None, "savez_id": savez_id},
}
if ut in KLUB_USER_TYPES and klub_id:
return {
"tenant_id": klub_id,
"tenant_type": "klub",
"tenant_name": (db_one("SELECT naziv FROM pgz_sport.klubovi WHERE id=%s",(klub_id,)) or {}).get("naziv"),
"tenant_scope": {"klub_id": klub_id, "savez_id": savez_id},
}
# super_admin without context
if ut == "super_admin":
return {"tenant_id": None, "tenant_type": "global",
"tenant_name": "Global", "tenant_scope": {"klub_id": None, "savez_id": None}}
return {"tenant_id": None, "tenant_type": "viewer",
"tenant_name": None, "tenant_scope": {"klub_id": klub_id, "savez_id": savez_id}}
# ─────────────────────────── JWT issue / verify ───────────────────────────
def _now() -> datetime: return datetime.now(timezone.utc)
def _new_jti() -> str: return secrets.token_urlsafe(16)
def make_access_token(u: Dict, jti: str) -> str:
tenant = _resolve_tenant(u)
tier = _tier_for(u.get("user_type") or "")
now = _now()
payload = {
"sub": str(u["id"]),
"uid": u["id"],
"email": u["email"],
"name": u.get("full_name") or ((u.get("ime") or "") + " " + (u.get("prezime") or "")).strip() or u["email"],
"tenant_id": tenant["tenant_id"],
"tenant_type": tenant["tenant_type"],
"tenant_name": tenant["tenant_name"],
"tenant_scope": tenant["tenant_scope"],
"role": u.get("user_type") or "viewer",
"tier": tier,
"jti": jti,
"typ": "access",
"iat": int(now.timestamp()),
"nbf": int(now.timestamp()),
"exp": int((now + ACCESS_TTL).timestamp()),
}
return _jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALG)
def make_refresh_token(uid: int, jti: str) -> str:
now = _now()
return _jwt.encode({
"sub": str(uid), "uid": uid, "jti": jti, "typ": "refresh",
"iat": int(now.timestamp()),
"exp": int((now + REFRESH_TTL).timestamp()),
}, JWT_SECRET, algorithm=JWT_ALG)
def decode_token(token: str) -> Dict:
try:
return _jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALG])
except _jwt.ExpiredSignatureError:
raise HTTPException(401, "Token expired")
except Exception as e:
raise HTTPException(401, f"Invalid token: {e}")
def _record_session(uid: int, jti: str, expires: datetime, ip: str = None, ua: str = None):
th = hashlib.sha256(jti.encode()).hexdigest()
db_exec("""INSERT INTO pgz_sport.user_sessions
(user_id, token_hash, device_info, ip_address, expires_at, revoked)
VALUES (%s,%s,%s,%s::inet,%s,false)
ON CONFLICT (token_hash) DO NOTHING""",
(uid, th, ua, ip, expires))
def _is_revoked(jti: str) -> bool:
th = hashlib.sha256(jti.encode()).hexdigest()
r = db_one("SELECT revoked FROM pgz_sport.user_sessions WHERE token_hash=%s", (th,))
if not r: return False
return bool(r.get("revoked"))
def _revoke_jti(jti: str):
th = hashlib.sha256(jti.encode()).hexdigest()
db_exec("UPDATE pgz_sport.user_sessions SET revoked=true WHERE token_hash=%s", (th,))
# ─────────────────────────── current_user dep ───────────────────────────
def _extract_token(authorization: Optional[str]) -> Optional[str]:
if not authorization: return None
return authorization.replace("Bearer ", "").strip() or None
def get_current_user(authorization: Optional[str] = Header(None)) -> Optional[Dict]:
token = _extract_token(authorization)
if not token: return None
try:
payload = decode_token(token)
except HTTPException:
return None
if payload.get("typ") not in (None, "access"):
return None
if _is_revoked(payload.get("jti","")):
return None
uid = payload.get("uid") or int(payload.get("sub", 0) or 0)
u = db_one("""SELECT id, email, full_name, ime, prezime, user_type,
klub_id, savez_id, status, aktivan, must_change_pwd
FROM pgz_sport.users WHERE id=%s""", (uid,))
if not u or u.get("status") != "active" or not u.get("aktivan", True):
return None
u["_jwt"] = payload
u["_token"] = token
return u
def require_user(user = Depends(get_current_user)) -> Dict:
if not user:
raise HTTPException(401, "Authentication required")
return user
def require_role(roles: List[str]):
def dep(user = Depends(require_user)):
if user.get("user_type") not in roles:
raise HTTPException(403, f"Forbidden — required: {','.join(roles)}")
return user
return dep
# ─────────────────────────── Audit ───────────────────────────
def audit(user_id: Optional[int], action: str, resource_type: str = None,
resource_id: int = None, meta: Dict = None, ip: str = None, ua: str = None):
try:
db_exec("""INSERT INTO pgz_sport.audit_events
(user_id, action, resource_type, resource_id, meta, ip_address, user_agent)
VALUES (%s,%s,%s,%s,%s::jsonb,%s::inet,%s)""",
(user_id, action, resource_type, resource_id,
json.dumps(meta or {}), ip, ua))
except Exception as e:
print(f"[AUDIT WARN] {e}")
def _client(req: Request):
ip = (req.headers.get("x-forwarded-for") or req.client.host or "").split(",")[0].strip() or None
ua = req.headers.get("user-agent")
return ip, ua
# ─────────────────────────── Schemas ───────────────────────────
class LoginReq(BaseModel):
email: str
password: str
totp: Optional[str] = None # 6-digit TOTP if 2FA enabled (or recovery code)
class RefreshReq(BaseModel):
refresh_token: str
class ChangePwdReq(BaseModel):
old_password: Optional[str] = None
new_password: str
class ResetPwdReq(BaseModel):
email: str
# ─────────────────────────── Rate limiting (R6 #5) ───────────────────────────
LOCK_THRESHOLD = int(os.environ.get("PGZ_LOGIN_LOCK_THRESHOLD", "5"))
LOCK_MINUTES = int(os.environ.get("PGZ_LOGIN_LOCK_MINUTES", "5"))
IP_THRESHOLD = int(os.environ.get("PGZ_LOGIN_IP_THRESHOLD", "10"))
IP_WINDOW_SEC = int(os.environ.get("PGZ_LOGIN_IP_WINDOW_SEC", "300")) # 5 min
# In-memory IP throttle: ip → list[float fail timestamps within window]
_ip_fail_log: Dict[str, List[float]] = {}
def _ip_record_fail(ip: Optional[str]):
if not ip: return
now = time.time()
arr = [t for t in _ip_fail_log.get(ip, []) if now - t < IP_WINDOW_SEC]
arr.append(now)
_ip_fail_log[ip] = arr
def _ip_blocked(ip: Optional[str]) -> Optional[int]:
"""Return seconds-until-unblock, or None if not blocked."""
if not ip: return None
now = time.time()
arr = [t for t in _ip_fail_log.get(ip, []) if now - t < IP_WINDOW_SEC]
_ip_fail_log[ip] = arr
if len(arr) < IP_THRESHOLD: return None
oldest = min(arr)
return max(1, int(IP_WINDOW_SEC - (now - oldest)))
def _ip_clear(ip: Optional[str]):
if ip and ip in _ip_fail_log:
_ip_fail_log.pop(ip, None)
# ─────────────────────────── Endpoints ───────────────────────────
@router.post("/login")
def login(req: LoginReq, request: Request):
ip, ua = _client(request)
email = (req.email or "").lower().strip()
if not email or not req.password:
raise HTTPException(400, "Email i lozinka obavezni")
# R6 #5: per-IP throttle (stops brute-force across many emails)
blocked_for = _ip_blocked(ip)
if blocked_for:
audit(None, "login.ratelimit.ip",
meta={"email": email, "ip": ip, "block_seconds": blocked_for},
ip=ip, ua=ua)
raise HTTPException(429, f"Previše pokušaja s ove IP adrese — pokušajte za {blocked_for}s")
u = db_one("""SELECT id, email, full_name, ime, prezime, password_hash, status,
user_type, klub_id, savez_id, aktivan, must_change_pwd,
failed_login_count, locked_until
FROM pgz_sport.users WHERE LOWER(email)=%s""", (email,))
if not u:
_ip_record_fail(ip)
audit(None, "login.fail", meta={"email": email, "reason": "no_user"}, ip=ip, ua=ua)
raise HTTPException(401, "Neispravni podaci")
if u.get("locked_until"):
lu = u["locked_until"]
if lu.tzinfo is None: lu = lu.replace(tzinfo=timezone.utc)
if lu > _now():
audit(u["id"], "login.locked", ip=ip, ua=ua)
raise HTTPException(423, "Račun privremeno zaključan")
if u.get("status") != "active" or not u.get("aktivan", True):
audit(u["id"], "login.fail", meta={"reason":"inactive"}, ip=ip, ua=ua)
raise HTTPException(403, "Račun nije aktivan")
if not verify_password(req.password, u.get("password_hash")):
# R6 #5: 5 fails → 5-minute lockout
new_fails = (u.get("failed_login_count") or 0) + 1
will_lock = new_fails >= LOCK_THRESHOLD
db_exec("""UPDATE pgz_sport.users
SET failed_login_count = %s,
locked_until = CASE WHEN %s
THEN now() + (interval '1 minute' * %s)
ELSE locked_until END
WHERE id=%s""",
(new_fails, will_lock, LOCK_MINUTES, u["id"]))
_ip_record_fail(ip)
audit(u["id"], "login.fail",
meta={"reason":"bad_password", "fails": new_fails,
"locked": bool(will_lock),
"lock_minutes": LOCK_MINUTES if will_lock else 0},
ip=ip, ua=ua)
raise HTTPException(401,
f"Neispravni podaci ({new_fails}/{LOCK_THRESHOLD})" +
(f" — račun je zaključan na {LOCK_MINUTES} minuta" if will_lock else ""))
# opportunistic rehash to bcrypt
if needs_rehash(u.get("password_hash")):
try:
db_exec("UPDATE pgz_sport.users SET password_hash=%s WHERE id=%s",
(hash_password(req.password), u["id"]))
except Exception: pass
# 2FA gate — if user has enabled 2FA, demand a valid TOTP / recovery code
twofa_row = None
try:
twofa_row = db_one("SELECT secret, enabled, recovery_codes FROM pgz_sport.user_2fa WHERE user_id=%s",
(u["id"],))
except Exception: pass
if twofa_row and twofa_row.get("enabled"):
code = (req.totp or "").strip().replace(" ", "")
if not code:
audit(u["id"], "login.2fa_required", ip=ip, ua=ua)
raise HTTPException(401, "2FA_REQUIRED")
ok = False
if code.isdigit() and len(code) in (6, 8) and HAS_PYOTP:
ok = _pyotp.TOTP(twofa_row["secret"]).verify(code, valid_window=1)
if not ok and twofa_row.get("recovery_codes"):
up = code.upper()
if up in (twofa_row["recovery_codes"] or []):
ok = True
# consume the recovery code so it can't be reused
remaining = [c for c in twofa_row["recovery_codes"] if c != up]
db_exec("UPDATE pgz_sport.user_2fa SET recovery_codes=%s, updated_at=now() WHERE user_id=%s",
(remaining, u["id"]))
if not ok:
audit(u["id"], "login.2fa_fail", ip=ip, ua=ua)
raise HTTPException(401, "Neispravan 2FA kod")
db_exec("""UPDATE pgz_sport.users
SET failed_login_count=0, locked_until=NULL, last_login=now()
WHERE id=%s""", (u["id"],))
_ip_clear(ip) # successful login clears IP throttle
jti = _new_jti()
rjti = _new_jti()
access = make_access_token(u, jti)
refresh = make_refresh_token(u["id"], rjti)
_record_session(u["id"], jti, _now() + ACCESS_TTL, ip=ip, ua=ua)
_record_session(u["id"], rjti, _now() + REFRESH_TTL, ip=ip, ua=(ua or "") + " [refresh]")
audit(u["id"], "login.ok", ip=ip, ua=ua)
tenant = _resolve_tenant(u)
return {
"access_token": access,
"refresh_token": refresh,
"token_type": "Bearer",
"expires_in": int(ACCESS_TTL.total_seconds()),
"user": {
"id": u["id"], "email": u["email"],
"full_name": u.get("full_name") or (u.get("ime","") + " " + u.get("prezime","")).strip(),
"role": u.get("user_type"), "tier": _tier_for(u.get("user_type") or ""),
"must_change_pwd": bool(u.get("must_change_pwd")),
**tenant,
},
}
@router.post("/refresh")
def refresh(req: RefreshReq, request: Request):
payload = decode_token(req.refresh_token)
if payload.get("typ") != "refresh":
raise HTTPException(401, "Invalid refresh token")
if _is_revoked(payload.get("jti","")):
raise HTTPException(401, "Refresh token revoked")
uid = payload.get("uid") or int(payload.get("sub", 0) or 0)
u = db_one("""SELECT id, email, full_name, ime, prezime, user_type,
klub_id, savez_id, status, aktivan, must_change_pwd
FROM pgz_sport.users WHERE id=%s""", (uid,))
if not u or u.get("status") != "active" or not u.get("aktivan", True):
raise HTTPException(401, "User inactive")
ip, ua = _client(request)
new_jti = _new_jti()
access = make_access_token(u, new_jti)
_record_session(u["id"], new_jti, _now() + ACCESS_TTL, ip=ip, ua=ua)
audit(u["id"], "auth.refresh", ip=ip, ua=ua)
return {"access_token": access, "token_type": "Bearer",
"expires_in": int(ACCESS_TTL.total_seconds())}
@router.post("/logout")
def logout(request: Request, user = Depends(require_user)):
jti = (user.get("_jwt") or {}).get("jti")
if jti: _revoke_jti(jti)
# Also revoke refresh tokens for this user (best-effort)
db_exec("""UPDATE pgz_sport.user_sessions SET revoked=true
WHERE user_id=%s AND device_info LIKE %s""",
(user["id"], "%[refresh]%"))
ip, ua = _client(request)
audit(user["id"], "logout", ip=ip, ua=ua)
return {"status": "ok"}
@router.get("/me")
def me(user = Depends(require_user)):
enriched = db_one("""SELECT id, email, full_name, ime, prezime, user_type,
klub_id, savez_id, must_change_pwd, aktivan, status,
last_login, oib, telefon, phone, preferred_language, created_at,
avatar_url, gdpr_consent_at, google_picture
FROM pgz_sport.users WHERE id=%s""", (user["id"],))
if not enriched:
raise HTTPException(404, "User not found")
tenant = _resolve_tenant(enriched)
roles = db_query("""SELECT r.code, r.naziv, ur.scope_type, ur.scope_id
FROM pgz_sport.user_roles ur JOIN pgz_sport.roles r ON r.id=ur.role_id
WHERE ur.user_id=%s AND ur.active=true""", (user["id"],))
try:
twofa = db_one("SELECT secret IS NOT NULL AS enabled FROM pgz_sport.user_2fa WHERE user_id=%s",
(user["id"],)) or {"enabled": False}
except Exception:
twofa = {"enabled": False}
return {**enriched,
"tier": _tier_for(enriched.get("user_type") or ""),
"must_change_pwd": bool(enriched.get("must_change_pwd")),
"two_factor_enabled": bool(twofa.get("enabled")),
**tenant, "roles": roles}
class UpdateMeReq(BaseModel):
ime: Optional[str] = None
prezime: Optional[str] = None
full_name: Optional[str] = None
telefon: Optional[str] = None
phone: Optional[str] = None
preferred_language: Optional[str] = None
oib: Optional[str] = None
@router.put("/me")
def update_me(req: UpdateMeReq, request: Request, user = Depends(require_user)):
fields = []
vals: List[Any] = []
for k in ("ime","prezime","full_name","telefon","phone","preferred_language","oib"):
v = getattr(req, k)
if v is not None:
fields.append(f"{k}=%s")
vals.append(v.strip() if isinstance(v, str) else v)
if not fields:
raise HTTPException(400, "Nema polja za ažuriranje")
vals.append(user["id"])
db_exec(f"UPDATE pgz_sport.users SET {', '.join(fields)}, updated_at=now() WHERE id=%s", tuple(vals))
ip, ua = _client(request)
audit(user["id"], "profile.update", meta={"fields": [f.split("=")[0] for f in fields]}, ip=ip, ua=ua)
# Re-fetch fresh user data and return same shape as GET /me
fresh = db_one("SELECT * FROM pgz_sport.users WHERE id=%s", (user["id"],))
if not fresh:
raise HTTPException(404, "User not found after update")
enriched = db_one("""SELECT id, email, full_name, ime, prezime, user_type,
klub_id, savez_id, must_change_pwd, aktivan, status,
last_login, oib, telefon, phone, preferred_language, created_at,
avatar_url, gdpr_consent_at, google_picture
FROM pgz_sport.users WHERE id=%s""", (user["id"],))
tenant = _resolve_tenant(enriched)
roles = db_query("""SELECT r.code, r.naziv, ur.scope_type, ur.scope_id
FROM pgz_sport.user_roles ur JOIN pgz_sport.roles r ON r.id=ur.role_id
WHERE ur.user_id=%s AND ur.active=true""", (user["id"],))
try:
twofa = db_one("SELECT secret IS NOT NULL AS enabled FROM pgz_sport.user_2fa WHERE user_id=%s",
(user["id"],)) or {"enabled": False}
except Exception:
twofa = {"enabled": False}
return {**enriched,
"tier": _tier_for(enriched.get("user_type") or ""),
"must_change_pwd": bool(enriched.get("must_change_pwd")),
"two_factor_enabled": bool(twofa.get("enabled")),
**tenant, "roles": roles}
# ─────────────────────────── AVATAR UPLOAD ───────────────────────────
import shutil, pathlib
from fastapi import UploadFile, File
UPLOAD_ROOT = pathlib.Path("/opt/pgz-sport/uploads")
AVATAR_DIR = UPLOAD_ROOT / "avatars"
AVATAR_DIR.mkdir(parents=True, exist_ok=True)
ALLOWED_AVATAR_MIME = {"image/jpeg","image/jpg","image/png","image/webp"}
ALLOWED_AVATAR_EXT = {".jpg",".jpeg",".png",".webp"}
MAX_AVATAR_BYTES = 5 * 1024 * 1024 # 5 MB
@router.post("/me/avatar")
async def upload_my_avatar(request: Request, file: UploadFile = File(...), user = Depends(require_user)):
ct = (file.content_type or "").lower()
if ct not in ALLOWED_AVATAR_MIME:
raise HTTPException(400, f"Nedozvoljen tip slike: {ct} — jpeg/png/webp")
ext = pathlib.Path(file.filename or "").suffix.lower()
if ext not in ALLOWED_AVATAR_EXT:
ext = {"image/jpeg":".jpg","image/jpg":".jpg","image/png":".png","image/webp":".webp"}.get(ct, ".jpg")
data = await file.read()
if len(data) > MAX_AVATAR_BYTES:
raise HTTPException(413, f"Slika prevelika ({len(data)} B > {MAX_AVATAR_BYTES})")
if len(data) < 32:
raise HTTPException(400, "Slika prazna ili neispravna")
safe_name = f"{int(user['id'])}_{int(time.time())}{ext}"
target = AVATAR_DIR / safe_name
with open(target, "wb") as f:
f.write(data)
try: os.chmod(target, 0o644)
except Exception: pass
avatar_url = f"/uploads/avatars/{safe_name}"
db_exec("UPDATE pgz_sport.users SET avatar_url=%s, updated_at=now() WHERE id=%s",
(avatar_url, user["id"]))
ip, ua = _client(request)
audit(user["id"], "profile.avatar_upload",
meta={"file": safe_name, "size": len(data), "mime": ct}, ip=ip, ua=ua)
return {"status":"ok", "avatar_url": avatar_url, "size": len(data), "mime": ct}
@router.delete("/me/avatar")
def delete_my_avatar(request: Request, user = Depends(require_user)):
cur = db_one("SELECT avatar_url FROM pgz_sport.users WHERE id=%s", (user["id"],))
if cur and cur.get("avatar_url"):
p = AVATAR_DIR / pathlib.Path(cur["avatar_url"]).name
try:
if p.exists() and p.is_relative_to(AVATAR_DIR): p.unlink()
except Exception: pass
db_exec("UPDATE pgz_sport.users SET avatar_url=NULL, updated_at=now() WHERE id=%s", (user["id"],))
ip, ua = _client(request)
audit(user["id"], "profile.avatar_delete", ip=ip, ua=ua)
return {"status": "ok"}
@router.post("/password/change")
def change_password(req: ChangePwdReq, request: Request, user = Depends(require_user)):
if len(req.new_password) < 8:
raise HTTPException(400, "Lozinka mora imati barem 8 znakova")
cur = db_one("SELECT password_hash, must_change_pwd FROM pgz_sport.users WHERE id=%s",
(user["id"],))
if not cur: raise HTTPException(404, "User not found")
if not cur.get("must_change_pwd"):
if not req.old_password:
raise HTTPException(400, "old_password obavezan")
if not verify_password(req.old_password, cur.get("password_hash")):
raise HTTPException(401, "Stara lozinka netočna")
db_exec("""UPDATE pgz_sport.users
SET password_hash=%s, must_change_pwd=false, updated_at=now()
WHERE id=%s""", (hash_password(req.new_password), user["id"]))
ip, ua = _client(request)
audit(user["id"], "password.change", ip=ip, ua=ua)
return {"status": "ok"}
@router.post("/password/reset")
def password_reset(req: ResetPwdReq, request: Request):
"""Issue a temporary password (admin-equivalent self-reset; logged)."""
email = (req.email or "").lower().strip()
u = db_one("SELECT id, email, aktivan FROM pgz_sport.users WHERE LOWER(email)=%s",
(email,))
ip, ua = _client(request)
audit(u["id"] if u else None, "password.reset.request",
meta={"email": email, "found": bool(u)}, ip=ip, ua=ua)
# Generic response — do not leak which emails exist
return {"status": "ok",
"message": "Ako račun postoji, administrator će vam poslati instrukcije."}
# ─────────────────────────── R5 #2+#3: invite & reset tokens ───────────────────────────
def _ensure_token_table():
db_exec("""CREATE TABLE IF NOT EXISTS pgz_sport.user_action_tokens (
token_hash TEXT PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES pgz_sport.users(id) ON DELETE CASCADE,
kind TEXT NOT NULL, -- 'invite' | 'reset'
created_at TIMESTAMPTZ DEFAULT now(),
expires_at TIMESTAMPTZ NOT NULL,
used_at TIMESTAMPTZ,
created_by INTEGER REFERENCES pgz_sport.users(id),
ip TEXT,
meta JSONB
)""")
db_exec("""CREATE INDEX IF NOT EXISTS idx_action_tokens_user
ON pgz_sport.user_action_tokens (user_id, kind, used_at)""")
_ensure_token_table()
INVITE_TTL = timedelta(days=int(os.environ.get("PGZ_INVITE_TTL_DAYS", "7")))
RESET_TTL = timedelta(hours=int(os.environ.get("PGZ_RESET_TTL_HOURS", "2")))
def _make_action_token() -> str:
return secrets.token_urlsafe(32)
def _hash_action_token(t: str) -> str:
return hashlib.sha256(t.encode()).hexdigest()
def issue_action_token(user_id: int, kind: str, ttl: timedelta,
created_by: Optional[int] = None,
ip: Optional[str] = None,
meta: Optional[Dict] = None) -> str:
"""Create a one-time URL-safe token; only its sha256 is persisted."""
if kind not in ("invite", "reset"):
raise ValueError("kind must be invite|reset")
# Invalidate any prior unused tokens of same kind for this user
db_exec("""UPDATE pgz_sport.user_action_tokens SET used_at=now()
WHERE user_id=%s AND kind=%s AND used_at IS NULL""",
(user_id, kind))
raw = _make_action_token()
th = _hash_action_token(raw)
db_exec("""INSERT INTO pgz_sport.user_action_tokens
(token_hash, user_id, kind, expires_at, created_by, ip, meta)
VALUES (%s,%s,%s,%s,%s,%s,%s::jsonb)""",
(th, user_id, kind, _now() + ttl, created_by, ip, json.dumps(meta or {})))
return raw
def consume_action_token(raw: str, kind: str) -> Optional[Dict]:
"""Validate (kind/expiry/unused) and atomically mark used_at. Returns row dict if OK."""
th = _hash_action_token(raw)
row = db_one("""SELECT t.user_id, t.expires_at, t.used_at, t.kind, t.meta,
u.email, u.aktivan, u.status
FROM pgz_sport.user_action_tokens t
JOIN pgz_sport.users u ON u.id = t.user_id
WHERE t.token_hash=%s AND t.kind=%s""", (th, kind))
if not row: return None
if row["used_at"] is not None: return None
exp = row["expires_at"]
if exp.tzinfo is None: exp = exp.replace(tzinfo=timezone.utc)
if exp <= _now(): return None
db_exec("UPDATE pgz_sport.user_action_tokens SET used_at=now() WHERE token_hash=%s", (th,))
return row
def _build_link(path: str, token: str) -> str:
base = os.environ.get("PGZ_PUBLIC_BASE", "https://api.rinet.one/sport")
sep = '&' if '?' in path else '?'
return f"{base}{path}{sep}token={token}"
# ─────────────────────────── /auth/forgot-password ───────────────────────────
class ForgotPwdReq(BaseModel):
email: str
@router.post("/forgot-password")
def forgot_password(req: ForgotPwdReq, request: Request):
"""Always returns a generic message — never leaks which emails exist.
Issues a reset token only if the user exists and is active, then
sends a (mock) e-mail with the reset link."""
email = (req.email or "").lower().strip()
ip, ua = _client(request)
u = db_one("SELECT id, email, aktivan, status FROM pgz_sport.users WHERE LOWER(email)=%s",
(email,))
token = None
mail_result = None
if u and u.get("aktivan") and u.get("status") == "active":
token = issue_action_token(u["id"], "reset", RESET_TTL, ip=ip,
meta={"email": email})
reset_link = _build_link("/static/login.html?reset=1", token)
try:
from .mailer import send_password_reset
mail_result = send_password_reset(email, reset_link,
int(RESET_TTL.total_seconds()))
except Exception as e:
print(f"[forgot_password mail WARN] {e}")
audit(u["id"], "password.forgot.issue",
meta={"email": email,
"ttl_hours": RESET_TTL.total_seconds()/3600,
"mail_sent": bool(mail_result and mail_result.get("sent")),
"mail_mock": bool(mail_result and mail_result.get("mock"))},
ip=ip, ua=ua)
else:
audit(u["id"] if u else None, "password.forgot.miss",
meta={"email": email}, ip=ip, ua=ua)
# Generic response — do not leak account existence
resp = {"status": "ok",
"message": "Ako račun postoji, poslan je e-mail s linkom za promjenu lozinke."}
# Reveal link only on localhost or with explicit env flag (debugging).
# Real users get it via e-mail.
if token and (os.environ.get("PGZ_REVEAL_RESET_TOKEN") == "1" or
(request.client.host in ("127.0.0.1", "::1"))):
resp["reset_link"] = _build_link("/static/login.html?reset=1", token)
resp["expires_in_seconds"] = int(RESET_TTL.total_seconds())
resp["mail_mock"] = bool(mail_result and mail_result.get("mock"))
return resp
class ResetTokenReq(BaseModel):
token: str
new_password: str
@router.post("/reset-password")
def reset_password_with_token(req: ResetTokenReq, request: Request):
"""Consume a reset token and set a new password."""
if len(req.new_password or "") < 8:
raise HTTPException(400, "Lozinka mora imati barem 8 znakova")
row = consume_action_token(req.token, "reset")
ip, ua = _client(request)
if not row:
audit(None, "password.reset.fail",
meta={"reason": "invalid_or_expired_token"}, ip=ip, ua=ua)
raise HTTPException(400, "Token je nevažeći ili istekao")
if not row.get("aktivan") or row.get("status") != "active":
audit(row["user_id"], "password.reset.fail",
meta={"reason": "user_inactive"}, ip=ip, ua=ua)
raise HTTPException(403, "Račun nije aktivan")
db_exec("""UPDATE pgz_sport.users
SET password_hash=%s, must_change_pwd=false,
failed_login_count=0, locked_until=NULL, updated_at=now()
WHERE id=%s""", (hash_password(req.new_password), row["user_id"]))
# Revoke all active sessions for safety
db_exec("UPDATE pgz_sport.user_sessions SET revoked=true WHERE user_id=%s",
(row["user_id"],))
audit(row["user_id"], "password.reset.ok", ip=ip, ua=ua)
return {"status": "ok", "email": row["email"]}
@router.get("/reset-password")
def reset_password_check(token: str, request: Request):
"""Pre-flight: validate that the token exists and isn't expired/used.
Does NOT consume the token."""
th = _hash_action_token(token)
row = db_one("""SELECT t.user_id, t.expires_at, t.used_at, u.email
FROM pgz_sport.user_action_tokens t
JOIN pgz_sport.users u ON u.id = t.user_id
WHERE t.token_hash=%s AND t.kind='reset'""", (th,))
if not row:
raise HTTPException(404, "Token nije pronađen")
if row["used_at"] is not None:
raise HTTPException(410, "Token je već iskorišten")
exp = row["expires_at"]
if exp.tzinfo is None: exp = exp.replace(tzinfo=timezone.utc)
if exp <= _now():
raise HTTPException(410, "Token je istekao")
return {"status": "ok", "email": row["email"], "expires_at": row["expires_at"].isoformat()}
# ─────────────────────────── /auth/setup-password (invite) ───────────────────────────
class SetupPwdReq(BaseModel):
token: str
new_password: str
@router.get("/setup-password")
def setup_password_check(token: str, request: Request):
"""Pre-flight: validate an invite token without consuming it."""
th = _hash_action_token(token)
row = db_one("""SELECT t.user_id, t.expires_at, t.used_at, u.email, u.full_name, u.user_type
FROM pgz_sport.user_action_tokens t
JOIN pgz_sport.users u ON u.id = t.user_id
WHERE t.token_hash=%s AND t.kind='invite'""", (th,))
if not row:
raise HTTPException(404, "Pozivnica nije pronađena")
if row["used_at"] is not None:
raise HTTPException(410, "Pozivnica je već iskorištena")
exp = row["expires_at"]
if exp.tzinfo is None: exp = exp.replace(tzinfo=timezone.utc)
if exp <= _now():
raise HTTPException(410, "Pozivnica je istekla")
return {"status": "ok",
"email": row["email"],
"full_name": row["full_name"],
"user_type": row["user_type"],
"expires_at": row["expires_at"].isoformat()}
@router.post("/setup-password")
def setup_password_consume(req: SetupPwdReq, request: Request):
"""Consume an invite token and set the user's first password."""
if len(req.new_password or "") < 8:
raise HTTPException(400, "Lozinka mora imati barem 8 znakova")
row = consume_action_token(req.token, "invite")
ip, ua = _client(request)
if not row:
audit(None, "invite.consume.fail",
meta={"reason": "invalid_or_expired_token"}, ip=ip, ua=ua)
raise HTTPException(400, "Pozivnica je nevažeća ili istekla")
if not row.get("aktivan") or row.get("status") != "active":
audit(row["user_id"], "invite.consume.fail",
meta={"reason": "user_inactive"}, ip=ip, ua=ua)
raise HTTPException(403, "Račun nije aktivan")
db_exec("""UPDATE pgz_sport.users
SET password_hash=%s, must_change_pwd=false,
email_verified=true,
failed_login_count=0, locked_until=NULL, updated_at=now()
WHERE id=%s""", (hash_password(req.new_password), row["user_id"]))
audit(row["user_id"], "invite.consume.ok",
meta={"email": row["email"]}, ip=ip, ua=ua)
return {"status": "ok", "email": row["email"]}
# ─────────────────────────── 2FA — real TOTP (RFC 6238) ───────────────────────────
try:
import pyotp as _pyotp
HAS_PYOTP = True
except Exception:
HAS_PYOTP = False
def _ensure_2fa_table():
db_exec("""CREATE TABLE IF NOT EXISTS pgz_sport.user_2fa (
user_id INTEGER PRIMARY KEY REFERENCES pgz_sport.users(id) ON DELETE CASCADE,
secret TEXT NOT NULL,
enabled BOOLEAN DEFAULT false,
verified_at TIMESTAMPTZ,
recovery_codes TEXT[],
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
)""")
_ensure_2fa_table()
def _build_qr_png(otpauth_url: str) -> str:
"""Return a data: URL containing a base64 PNG of the QR code."""
try:
import qrcode, io, base64
img = qrcode.make(otpauth_url)
buf = io.BytesIO()
img.save(buf, format="PNG")
return "data:image/png;base64," + base64.b64encode(buf.getvalue()).decode()
except Exception as e:
return ""
def _gen_recovery_codes(n: int = 8) -> List[str]:
return [secrets.token_hex(4).upper() for _ in range(n)]
@router.post("/2fa/setup")
def twofa_setup(user = Depends(require_user)):
"""Generate a TOTP secret, store unverified, and return otpauth URL + QR + recovery codes.
The 2FA stays disabled until /2fa/verify confirms a valid TOTP code."""
if not HAS_PYOTP:
raise HTTPException(503, "pyotp not installed on server")
secret = _pyotp.random_base32() # 32-char base32, RFC 4648 — what authenticator apps expect
recovery = _gen_recovery_codes()
db_exec("""INSERT INTO pgz_sport.user_2fa (user_id, secret, enabled, recovery_codes, updated_at)
VALUES (%s,%s,false,%s,now())
ON CONFLICT (user_id) DO UPDATE SET
secret=EXCLUDED.secret, enabled=false,
recovery_codes=EXCLUDED.recovery_codes, updated_at=now()""",
(user["id"], secret, recovery))
issuer = "PGŽ Sport"
otpauth = _pyotp.TOTP(secret).provisioning_uri(name=user["email"], issuer_name=issuer)
return {
"secret": secret,
"otpauth_url": otpauth,
"qr_png": _build_qr_png(otpauth),
"issuer": issuer,
"account": user["email"],
"recovery_codes": recovery,
"enabled": False,
"instructions": "Skenirajte QR u Google Authenticator / Authy / 1Password, zatim potvrdite kod kroz POST /api/auth/2fa/verify",
}
class TwoFAVerifyReq(BaseModel):
code: str
@router.post("/2fa/verify")
def twofa_verify(req: TwoFAVerifyReq, request: Request, user = Depends(require_user)):
"""Verify TOTP code; on success, mark 2FA enabled."""
if not HAS_PYOTP:
raise HTTPException(503, "pyotp not installed on server")
row = db_one("SELECT secret, enabled FROM pgz_sport.user_2fa WHERE user_id=%s",
(user["id"],))
if not row:
raise HTTPException(400, "2FA nije postavljen — pozovite /2fa/setup prvo")
code = (req.code or "").strip().replace(" ", "")
if not code or not code.isdigit() or len(code) not in (6, 8):
raise HTTPException(400, "Neispravan format koda (6-8 znamenki)")
totp = _pyotp.TOTP(row["secret"])
# valid_window=1 → tolerate ±30s drift
if not totp.verify(code, valid_window=1):
ip, ua = _client(request)
audit(user["id"], "2fa.verify.fail", ip=ip, ua=ua)
raise HTTPException(401, "Neispravan TOTP kod")
db_exec("""UPDATE pgz_sport.user_2fa
SET enabled=true, verified_at=now(), updated_at=now()
WHERE user_id=%s""", (user["id"],))
ip, ua = _client(request)
audit(user["id"], "2fa.verify.ok", ip=ip, ua=ua)
return {"status": "ok", "enabled": True}
@router.post("/2fa/disable")
def twofa_disable(req: TwoFAVerifyReq, request: Request, user = Depends(require_user)):
"""Disable 2FA — must verify a current TOTP code (or recovery code)."""
if not HAS_PYOTP:
raise HTTPException(503, "pyotp not installed on server")
row = db_one("SELECT secret, recovery_codes FROM pgz_sport.user_2fa WHERE user_id=%s",
(user["id"],))
if not row:
raise HTTPException(404, "2FA nije postavljen")
code = (req.code or "").strip().replace(" ", "").upper()
valid = False
if code.isdigit() and len(code) in (6, 8):
valid = _pyotp.TOTP(row["secret"]).verify(code, valid_window=1)
elif row.get("recovery_codes") and code in (row["recovery_codes"] or []):
valid = True
if not valid:
raise HTTPException(401, "Neispravan kod")
db_exec("DELETE FROM pgz_sport.user_2fa WHERE user_id=%s", (user["id"],))
ip, ua = _client(request)
audit(user["id"], "2fa.disable", ip=ip, ua=ua)
return {"status": "ok", "enabled": False}
@router.get("/2fa/status")
def twofa_status(user = Depends(require_user)):
row = db_one("SELECT enabled, verified_at, created_at FROM pgz_sport.user_2fa WHERE user_id=%s",
(user["id"],))
return {"enabled": bool(row and row.get("enabled")),
"configured": bool(row),
"verified_at": row.get("verified_at") if row else None}
+5 -2
View File
@@ -1,4 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
from dotenv import load_dotenv
load_dotenv('/opt/rinet-gpu/.env.master')
# auto-added by patch_scrapers_with_dotenv.sh
""" """
seal.py — Polygon PoS sealing module for PGŽ Sport audit log seal.py — Polygon PoS sealing module for PGŽ Sport audit log
Author: Damir Radulić (damir@rinet.one) / dradulic@outlook.com Author: Damir Radulić (damir@rinet.one) / dradulic@outlook.com
@@ -34,7 +38,6 @@ list_seals(action=None, ref_type=None, ref_id=None, limit=50) -> list[dict]
The module is import-safe even on hosts without web3 installed; the LIVE branch The module is import-safe even on hosts without web3 installed; the LIVE branch
just becomes a no-op. just becomes a no-op.
""" """
from __future__ import annotations
import os import os
import json import json
@@ -77,7 +80,7 @@ DB = dict(
port=_pgp, port=_pgp,
dbname=os.environ.get("PG_DB", "rinet_v3"), dbname=os.environ.get("PG_DB", "rinet_v3"),
user=os.environ.get("PG_USER", "rinet"), user=os.environ.get("PG_USER", "rinet"),
password=os.environ.get("PG_PASS", "R1net2026!SecureDB#v7"), password=os.environ["DB_PASSWORD"],
) )
# ─── helpers ───────────────────────────────────────────────────────────── # ─── helpers ─────────────────────────────────────────────────────────────
+375
View File
@@ -0,0 +1,375 @@
#!/usr/bin/env python3
"""
seal.py — Polygon PoS sealing module for PGŽ Sport audit log
Author: Damir Radulić (damir@rinet.one) / dradulic@outlook.com
Date: 2026-05-04
Version: 1.0.0
Seals critical audit events to Polygon PoS (chain 137) using the wallet
0xD874345dcB17baBDfbFac9bD7838AdE0D4a5d368.
Two operating modes:
1. LIVE — environment provides POLYGON_PRIVKEY (and web3 is installed).
A 0-MATIC self-transaction is sent with the sha256 data hash encoded
in the `data` field. Returns the real 0x… 64-char tx hash.
2. PENDING — no key configured. The seal record is queued in
pgz_sport.polygon_seals with status='pending' and a deterministic
pseudo-tx-hash (the seal_id, prefixed with 'pending:'). A later
batch job (or operator) can flush the queue once a key is loaded.
Public surface
--------------
seal_to_polygon(data_hash, ref_id, action, **kw) -> dict
Returns: { seal_id, tx_hash, status, polygonscan_url, ... }
verify_seal(seal_id) -> dict
Read-back utility. Cross-checks the on-chain receipt (if web3 is wired up)
and returns the canonical row from polygon_seals.
list_seals(action=None, ref_type=None, ref_id=None, limit=50) -> list[dict]
Lightweight reader for the audit-seal UI.
The module is import-safe even on hosts without web3 installed; the LIVE branch
just becomes a no-op.
"""
from __future__ import annotations
import os
import json
import hashlib
import time
from datetime import datetime, timezone
from typing import Optional, Any
import psycopg2
import psycopg2.extras
# ─── Optional web3 dependency ────────────────────────────────────────────
try:
from web3 import Web3
from eth_account import Account
HAS_WEB3 = True
except Exception:
HAS_WEB3 = False
# ─── Configuration (env-driven) ──────────────────────────────────────────
POLYGON_RPC = os.environ.get("POLYGON_RPC", "https://polygon-rpc.com")
POLYGON_CHAIN_ID = int(os.environ.get("POLYGON_CHAIN_ID", "137"))
POLYGON_WALLET = os.environ.get(
"POLYGON_WALLET", "0xD874345dcB17baBDfbFac9bD7838AdE0D4a5d368"
).strip()
POLYGON_PRIVKEY = os.environ.get("POLYGON_PRIVKEY", "").strip()
POLYGONSCAN_BASE = os.environ.get("POLYGONSCAN_BASE", "https://polygonscan.com")
_pgh = os.environ.get("PG_HOST", "10.10.0.2")
_pgp = int(os.environ.get("PG_PORT", "6432"))
# pgz-sport.service inherits PG_HOST=localhost:5432 from /opt/.env.rinet which is
# stale (local PG was decommissioned). Honour the DB_HOST/DB_PORT override that
# points at canonical Server B (10.10.0.2:6432).
if _pgh in ("localhost", "127.0.0.1"):
_pgh = os.environ.get("DB_HOST", "10.10.0.2")
_pgp = int(os.environ.get("DB_PORT", "6432"))
DB = dict(
host=_pgh,
port=_pgp,
dbname=os.environ.get("PG_DB", "rinet_v3"),
user=os.environ.get("PG_USER", "rinet"),
password=os.environ["DB_PASSWORD"],
)
# ─── helpers ─────────────────────────────────────────────────────────────
def _db():
c = psycopg2.connect(**DB)
c.autocommit = True
return c
def _sha256(*parts: Any) -> str:
h = hashlib.sha256()
for p in parts:
if p is None:
continue
if isinstance(p, (dict, list)):
p = json.dumps(p, sort_keys=True, ensure_ascii=False, default=str)
h.update(str(p).encode("utf-8", errors="replace"))
h.update(b"\x00")
return h.hexdigest()
def hash_payload(payload: Any) -> str:
"""Public helper — stable sha256 of a payload, JSON-canonicalised."""
if isinstance(payload, (dict, list)):
payload = json.dumps(payload, sort_keys=True, ensure_ascii=False, default=str)
return hashlib.sha256(str(payload).encode("utf-8", errors="replace")).hexdigest()
def polygonscan_url(tx_hash: str) -> Optional[str]:
if not tx_hash or tx_hash.startswith("pending:"):
return None
if not tx_hash.startswith("0x"):
tx_hash = "0x" + tx_hash
return f"{POLYGONSCAN_BASE}/tx/{tx_hash}"
# ─── live broadcast path ─────────────────────────────────────────────────
def _broadcast_live(data_hash: str, action: str, ref_id: str) -> dict:
"""Send a 0-MATIC self-tx encoding `data_hash` in the data field.
Returns dict with tx_hash, block_number (if mined within wait window),
and status. Raises on RPC errors so the caller can fall back.
"""
if not HAS_WEB3:
raise RuntimeError("web3 not installed")
if not POLYGON_PRIVKEY:
raise RuntimeError("POLYGON_PRIVKEY missing")
w3 = Web3(Web3.HTTPProvider(POLYGON_RPC, request_kwargs={"timeout": 15}))
acct = Account.from_key(POLYGON_PRIVKEY)
if acct.address.lower() != POLYGON_WALLET.lower():
raise RuntimeError(
f"key/address mismatch: key={acct.address} wallet={POLYGON_WALLET}"
)
nonce = w3.eth.get_transaction_count(acct.address)
gas_price = w3.eth.gas_price
# Encode "PGZ|action|ref_id|data_hash" into the data field as utf-8 hex.
memo = f"PGZ|{action}|{ref_id}|0x{data_hash}".encode("utf-8")
tx = {
"to": acct.address,
"value": 0,
"data": "0x" + memo.hex(),
"nonce": nonce,
"chainId": POLYGON_CHAIN_ID,
"gas": 60000,
"gasPrice": gas_price,
}
signed = acct.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.rawTransaction).hex()
block_number = None
try:
receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=30)
block_number = int(receipt.blockNumber)
status = "confirmed" if receipt.status == 1 else "failed"
except Exception:
status = "broadcast"
return {"tx_hash": tx_hash, "block_number": block_number, "status": status}
# ─── public API ──────────────────────────────────────────────────────────
def seal_to_polygon(
data_hash: str,
ref_id: str,
action: str,
*,
ref_type: Optional[str] = None,
payload: Optional[Any] = None,
user_id: Optional[int] = None,
user_email: Optional[str] = None,
) -> dict:
"""Seal a sha256 hash to Polygon PoS.
Always persists a row in pgz_sport.polygon_seals. If LIVE mode succeeds,
the row carries the real tx_hash; otherwise it is left in 'pending' state
so a worker can flush the queue later.
Parameters
----------
data_hash : str
sha256 hex digest of the payload being sealed.
ref_id : str
opaque reference (e.g. "klub:42", "sufinanciranje:2026-001").
action : str
canonical action name (e.g. "sufinanciranje.approved").
"""
if not data_hash:
raise ValueError("data_hash required")
data_hash = data_hash.lower().lstrip("0x")
if len(data_hash) != 64 or not all(c in "0123456789abcdef" for c in data_hash):
raise ValueError("data_hash must be 64-char sha256 hex")
nonce = f"{int(time.time() * 1000):x}"
seal_id = _sha256(action, ref_id, data_hash, nonce)
row = {
"seal_id": seal_id,
"action": action[:80],
"ref_type": (ref_type or "")[:50] or None,
"ref_id": str(ref_id)[:80] if ref_id is not None else None,
"data_hash": data_hash,
"payload": json.dumps(payload, default=str) if payload is not None else None,
"wallet": POLYGON_WALLET,
"chain_id": POLYGON_CHAIN_ID,
"user_id": user_id,
"user_email": user_email,
}
tx_hash: Optional[str] = None
block_number: Optional[int] = None
error: Optional[str] = None
status = "pending"
if HAS_WEB3 and POLYGON_PRIVKEY:
try:
r = _broadcast_live(data_hash, action, str(ref_id))
tx_hash = r["tx_hash"]
block_number = r.get("block_number")
status = r.get("status", "broadcast")
except Exception as e:
error = f"{type(e).__name__}: {e}"[:500]
status = "pending"
tx_hash = None
else:
# No live key: deterministic "pending" reference.
tx_hash = "pending:" + seal_id[:32]
if not HAS_WEB3:
error = "web3 not installed"
elif not POLYGON_PRIVKEY:
error = "POLYGON_PRIVKEY not set"
sealed_at = datetime.now(timezone.utc) if status in ("broadcast", "confirmed") else None
with _db() as c, c.cursor() as cur:
cur.execute(
"""
INSERT INTO pgz_sport.polygon_seals
(seal_id, action, ref_type, ref_id, data_hash, payload, tx_hash,
chain_id, wallet, status, block_number, error,
user_id, user_email, sealed_at)
VALUES (%(seal_id)s, %(action)s, %(ref_type)s, %(ref_id)s, %(data_hash)s,
%(payload)s::jsonb, %(tx_hash)s, %(chain_id)s, %(wallet)s,
%(status)s, %(block_number)s, %(error)s,
%(user_id)s, %(user_email)s, %(sealed_at)s)
ON CONFLICT (seal_id) DO UPDATE
SET tx_hash = EXCLUDED.tx_hash,
status = EXCLUDED.status,
block_number = EXCLUDED.block_number,
error = EXCLUDED.error,
sealed_at = EXCLUDED.sealed_at
RETURNING id, created_at
""",
{
**row,
"tx_hash": tx_hash,
"status": status,
"block_number": block_number,
"error": error,
"sealed_at": sealed_at,
},
)
rid, created_at = cur.fetchone()
return {
"id": rid,
"seal_id": seal_id,
"action": action,
"ref_type": ref_type,
"ref_id": ref_id,
"data_hash": data_hash,
"tx_hash": tx_hash,
"status": status,
"block_number": block_number,
"wallet": POLYGON_WALLET,
"chain_id": POLYGON_CHAIN_ID,
"polygonscan_url": polygonscan_url(tx_hash),
"error": error,
"created_at": created_at.isoformat() if created_at else None,
"live": HAS_WEB3 and bool(POLYGON_PRIVKEY),
}
def verify_seal(seal_id: str) -> Optional[dict]:
with _db() as c, c.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur:
cur.execute(
"""SELECT id, seal_id, action, ref_type, ref_id, data_hash, tx_hash,
chain_id, wallet, status, block_number, error,
user_id, user_email, created_at, sealed_at, payload
FROM pgz_sport.polygon_seals WHERE seal_id=%s""",
(seal_id,),
)
row = cur.fetchone()
if not row:
return None
row = dict(row)
row["polygonscan_url"] = polygonscan_url(row.get("tx_hash"))
if row.get("created_at"):
row["created_at"] = row["created_at"].isoformat()
if row.get("sealed_at"):
row["sealed_at"] = row["sealed_at"].isoformat()
if HAS_WEB3 and row.get("tx_hash") and not str(row["tx_hash"]).startswith("pending:"):
try:
w3 = Web3(Web3.HTTPProvider(POLYGON_RPC, request_kwargs={"timeout": 8}))
r = w3.eth.get_transaction_receipt(row["tx_hash"])
row["onchain"] = {
"block_number": int(r.blockNumber),
"status": int(r.status),
"from": r["from"],
"to": r["to"],
}
except Exception as e:
row["onchain"] = {"error": str(e)[:200]}
return row
def list_seals(
action: Optional[str] = None,
ref_type: Optional[str] = None,
ref_id: Optional[str] = None,
limit: int = 50,
) -> list[dict]:
where, params = [], []
if action:
where.append("action = %s")
params.append(action)
if ref_type:
where.append("ref_type = %s")
params.append(ref_type)
if ref_id is not None:
where.append("ref_id = %s")
params.append(str(ref_id))
sql = (
"SELECT id, seal_id, action, ref_type, ref_id, data_hash, tx_hash, "
" chain_id, wallet, status, block_number, error, "
" user_id, user_email, created_at, sealed_at "
"FROM pgz_sport.polygon_seals "
+ ("WHERE " + " AND ".join(where) + " " if where else "")
+ "ORDER BY id DESC LIMIT %s"
)
params.append(min(int(limit or 50), 500))
with _db() as c, c.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur:
cur.execute(sql, params)
rows = [dict(r) for r in cur.fetchall()]
for r in rows:
r["polygonscan_url"] = polygonscan_url(r.get("tx_hash"))
if r.get("created_at"):
r["created_at"] = r["created_at"].isoformat()
if r.get("sealed_at"):
r["sealed_at"] = r["sealed_at"].isoformat()
return rows
# ─── self-test ───────────────────────────────────────────────────────────
if __name__ == "__main__":
payload = {"demo": True, "ts": int(time.time()), "msg": "PGŽ seal self-test"}
h = hash_payload(payload)
res = seal_to_polygon(
h,
ref_id="selftest:1",
action="selftest.run",
ref_type="selftest",
payload=payload,
)
print(json.dumps(res, indent=2, default=str, ensure_ascii=False))
+1
View File
@@ -0,0 +1 @@
{"version":"1.0","provider_name":"Rijecki sportski savez","provider_url":"https:\/\/rss.hr","author_name":"Slavisa Bradic","author_url":"https:\/\/rss.hr\/author\/slavisa-bradic\/","title":"Svjetska liga u umjetni\u010dkom plivanju","type":"rich","width":600,"height":338,"html":"<blockquote class=\"wp-embedded-content\" data-secret=\"T58xsoqCLk\"><a href=\"https:\/\/rss.hr\/svjetska-liga-u-umjetnickom-plivanju\/\">Svjetska liga u umjetni\u010dkom plivanju<\/a><\/blockquote><iframe sandbox=\"allow-scripts\" security=\"restricted\" src=\"https:\/\/rss.hr\/svjetska-liga-u-umjetnickom-plivanju\/embed\/#?secret=T58xsoqCLk\" width=\"600\" height=\"338\" title=\"&#8220;Svjetska liga u umjetni\u010dkom plivanju&#8221; &#8212; Rijecki sportski savez\" data-secret=\"T58xsoqCLk\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\" class=\"wp-embedded-content\"><\/iframe><script>\n\/*! This file is auto-generated *\/\n!function(d,l){\"use strict\";l.querySelector&&d.addEventListener&&\"undefined\"!=typeof URL&&(d.wp=d.wp||{},d.wp.receiveEmbedMessage||(d.wp.receiveEmbedMessage=function(e){var t=e.data;if((t||t.secret||t.message||t.value)&&!\/[^a-zA-Z0-9]\/.test(t.secret)){for(var s,r,n,a=l.querySelectorAll('iframe[data-secret=\"'+t.secret+'\"]'),o=l.querySelectorAll('blockquote[data-secret=\"'+t.secret+'\"]'),c=new RegExp(\"^https?:$\",\"i\"),i=0;i<o.length;i++)o[i].style.display=\"none\";for(i=0;i<a.length;i++)s=a[i],e.source===s.contentWindow&&(s.removeAttribute(\"style\"),\"height\"===t.message?(1e3<(r=parseInt(t.value,10))?r=1e3:~~r<200&&(r=200),s.height=r):\"link\"===t.message&&(r=new URL(s.getAttribute(\"src\")),n=new URL(t.value),c.test(n.protocol))&&n.host===r.host&&l.activeElement===s&&(d.top.location.href=t.value))}},d.addEventListener(\"message\",d.wp.receiveEmbedMessage,!1),l.addEventListener(\"DOMContentLoaded\",function(){for(var e,t,s=l.querySelectorAll(\"iframe.wp-embedded-content\"),r=0;r<s.length;r++)(t=(e=s[r]).getAttribute(\"data-secret\"))||(t=Math.random().toString(36).substring(2,12),e.src+=\"#?secret=\"+t,e.setAttribute(\"data-secret\",t)),e.contentWindow.postMessage({message:\"ready\",secret:t},\"*\")},!1)))}(window,document);\n\/\/# sourceURL=https:\/\/rss.hr\/wp-includes\/js\/wp-embed.min.js\n<\/script>\n","thumbnail_url":"https:\/\/rss.hr\/wp-content\/uploads\/2024\/12\/IMG-20241216-WA00061-768x512.jpg","thumbnail_width":600,"thumbnail_height":400}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
+434
View File
@@ -0,0 +1,434 @@
.events-list {
list-style: none;
margin: 0 auto;
padding: 0;
}
.events-list .event {
border-bottom: 1px solid #5e5e5e;
margin-bottom: 1rem;
padding: 1rem 0;
display: flex;
gap: 20px;
}
.events-list .event .date {
padding: 1rem 3rem;
}
.events-list .event .date .day,
.events-list .event .date .month {
display: block;
text-align: center;
}
.events-list .event .date .day {
font-size: 2.1875rem;
font-weight: bold;
}
.events-list .event .date .month {}
.events-list .event .title {
margin: 0 auto;
padding: 0;
}
.events-list .event .description {
margin: .5rem auto;
padding: 0;
line-height: 1.3;
}
.events-list .event .images {
width: 200px;
height: 200px;
}
.events-list .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-list .event .event-info {
display: flex;
gap: 20px;
}
.events-grid .event {
float: left;
width: 31.33%;
margin: 1% 0 1% 3%;
border: none !important;
display: block;
}
.events-grid .event:after {
clear: both;
}
.events-grid .event:first-child {
margin-left: 0;
}
.events-grid .event .images {
display: block;
width: 100%;
height: 300px;
margin-bottom: 1rem;
position: relative;
}
.events-grid .event .images .date {
position: absolute;
left: 0;
bottom: 0;
color: #ffffff;
background-color: red;
padding: .8rem 1.5rem;
}
.events-grid .event .date .day {
font-size: 1.875rem;
font-weight: bold;
display: block;
text-align: center;
}
.events-grid .event .date .month {
display: block;
text-align: center;
}
.events-grid .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-grid .event .event-content .title {
font-size: 2rem;
line-height: 1.3;
margin: 0 auto 1rem;
}
.events-grid .event .event-content p {
font-size: .875rem;
line-height: 1.5;
}
.events-grid .event .buttons .btn-cta {
background-color: red;
color: #ffffff;
font-size: 1rem;
padding: .8rem 1.5rem;
cursor: pointer;
}
@media only screen and (max-width: 700px) {
.events-grid .event {
width: 48.5%;
margin: 1% 0 1% 3%;
}
}
@media only screen and (max-width: 480px) {
.events-grid .event {
float: none;
width: 100%;
margin: 0 auto 3%;
}
}
/**************************\
Basic Modal Styles
\**************************/
.modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: 99999;
}
.modal__container {
background-color: #fff;
padding: 30px;
width: 900px;
max-width: 90%;
max-height: 100vh;
border-radius: 4px;
overflow-y: auto;
box-sizing: border-box;
}
.modal__header {
display: flex;
justify-content: space-between;
align-items: center;
}
.modal__title {
margin-top: 0;
margin-bottom: 0;
font-weight: 600;
font-size: 1.25rem;
line-height: 1.25;
color: #000000;
box-sizing: border-box;
}
.modal__close {
background: transparent;
border: 0;
}
.modal__header .modal__close:before {
content: "\2715";
}
.modal__content {
margin-top: 2rem;
margin-bottom: 2rem;
line-height: 1.5;
color: rgba(0, 0, 0, .8);
}
.modal__btn {
font-size: .875rem;
padding-left: 1rem;
padding-right: 1rem;
padding-top: .5rem;
padding-bottom: .5rem;
background-color: #e6e6e6;
color: rgba(0, 0, 0, .8);
border-radius: .25rem;
border-style: none;
border-width: 0;
cursor: pointer;
-webkit-appearance: button;
text-transform: none;
overflow: visible;
line-height: 1.15;
margin: 0;
will-change: transform;
-moz-osx-font-smoothing: grayscale;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform: translateZ(0);
transform: translateZ(0);
transition: -webkit-transform .25s ease-out;
transition: transform .25s ease-out;
transition: transform .25s ease-out, -webkit-transform .25s ease-out;
}
.modal__btn:focus,
.modal__btn:hover {
-webkit-transform: scale(1.05);
transform: scale(1.05);
}
.modal__btn-primary {
background-color: #00449e;
color: #fff;
}
/**************************\
Demo Animation Style
\**************************/
@keyframes mmfadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes mmfadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes mmslideIn {
from {
transform: translateY(15%);
}
to {
transform: translateY(0);
}
}
@keyframes mmslideOut {
from {
transform: translateY(0);
}
to {
transform: translateY(-10%);
}
}
.micromodal-slide {
display: none;
}
.micromodal-slide.is-open {
display: block;
}
.micromodal-slide[aria-hidden="false"] .modal__overlay {
animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="false"] .modal__container {
animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__overlay {
animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__container {
animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide .modal__container,
.micromodal-slide .modal__overlay {
will-change: transform;
}
.micromodal-slide .title {
font-size: 1.5625rem;
color: #111111;
border: none;
font-weight: bold;
text-transform: uppercase;
margin: 0 auto;
padding: 0;
}
.micromodal-slide .images {
margin: .5rem auto 1.5rem;
width: 100%;
height: 350px;
overflow: hidden;
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-ms-border-radius: 10px;
-o-border-radius: 10px;
}
.micromodal-slide .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.micromodal-slide h2.title {
font-size: 1.25rem;
margin: 0 auto .5rem;
}
.micromodal-slide h4.title {
font-size: 1rem;
margin: 0 auto .5rem;
}
.micromodal-slide .short_description {
font-size: 1.125rem;
font-weight: lighter;
}
.micromodal-slide .long_description {
font-size: 1rem;
margin-bottom: 1rem;
}
.micromodal-slide table.period {
border: none;
border-collapse: collapse;
width: 100%;
}
.micromodal-slide table.period td strong {
display: block;
font-size: .8125rem;
}
.micromodal-slide table.period tr {
border-top: 1px solid #dddddd;
}
.micromodal-slide table.period tr.noborder {
border: none;
}
.micromodal-slide table.period td {
border: none;
font-size: 1rem;
vertical-align: top;
padding: 1rem .5rem;
}
.micromodal-slide table.period tr.noborder td {
padding: 0 .5rem 1rem;
}
.micromodal-slide span.tag {
font-size: .8125rem;
padding: 5px 8px;
color: #ffffff;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
.micromodal-slide span.tag.canceled {
background-color: #FF0000;
}
.micromodal-slide span.tag.free-entry {
background-color: #28A745;
}
.micromodal-slide span.tag.limited {
background-color: #1668B2;
}
.micromodal-slide .tags {
margin-right: 1rem;
background-color: #f2f2f2;
font-size: .8125rem;
padding: 5px 8px;
color: #111111;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
+434
View File
@@ -0,0 +1,434 @@
.events-list {
list-style: none;
margin: 0 auto;
padding: 0;
}
.events-list .event {
border-bottom: 1px solid #5e5e5e;
margin-bottom: 1rem;
padding: 1rem 0;
display: flex;
gap: 20px;
}
.events-list .event .date {
padding: 1rem 3rem;
}
.events-list .event .date .day,
.events-list .event .date .month {
display: block;
text-align: center;
}
.events-list .event .date .day {
font-size: 2.1875rem;
font-weight: bold;
}
.events-list .event .date .month {}
.events-list .event .title {
margin: 0 auto;
padding: 0;
}
.events-list .event .description {
margin: .5rem auto;
padding: 0;
line-height: 1.3;
}
.events-list .event .images {
width: 200px;
height: 200px;
}
.events-list .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-list .event .event-info {
display: flex;
gap: 20px;
}
.events-grid .event {
float: left;
width: 31.33%;
margin: 1% 0 1% 3%;
border: none !important;
display: block;
}
.events-grid .event:after {
clear: both;
}
.events-grid .event:first-child {
margin-left: 0;
}
.events-grid .event .images {
display: block;
width: 100%;
height: 300px;
margin-bottom: 1rem;
position: relative;
}
.events-grid .event .images .date {
position: absolute;
left: 0;
bottom: 0;
color: #ffffff;
background-color: red;
padding: .8rem 1.5rem;
}
.events-grid .event .date .day {
font-size: 1.875rem;
font-weight: bold;
display: block;
text-align: center;
}
.events-grid .event .date .month {
display: block;
text-align: center;
}
.events-grid .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-grid .event .event-content .title {
font-size: 2rem;
line-height: 1.3;
margin: 0 auto 1rem;
}
.events-grid .event .event-content p {
font-size: .875rem;
line-height: 1.5;
}
.events-grid .event .buttons .btn-cta {
background-color: red;
color: #ffffff;
font-size: 1rem;
padding: .8rem 1.5rem;
cursor: pointer;
}
@media only screen and (max-width: 700px) {
.events-grid .event {
width: 48.5%;
margin: 1% 0 1% 3%;
}
}
@media only screen and (max-width: 480px) {
.events-grid .event {
float: none;
width: 100%;
margin: 0 auto 3%;
}
}
/**************************\
Basic Modal Styles
\**************************/
.modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: 99999;
}
.modal__container {
background-color: #fff;
padding: 30px;
width: 900px;
max-width: 90%;
max-height: 100vh;
border-radius: 4px;
overflow-y: auto;
box-sizing: border-box;
}
.modal__header {
display: flex;
justify-content: space-between;
align-items: center;
}
.modal__title {
margin-top: 0;
margin-bottom: 0;
font-weight: 600;
font-size: 1.25rem;
line-height: 1.25;
color: #000000;
box-sizing: border-box;
}
.modal__close {
background: transparent;
border: 0;
}
.modal__header .modal__close:before {
content: "\2715";
}
.modal__content {
margin-top: 2rem;
margin-bottom: 2rem;
line-height: 1.5;
color: rgba(0, 0, 0, .8);
}
.modal__btn {
font-size: .875rem;
padding-left: 1rem;
padding-right: 1rem;
padding-top: .5rem;
padding-bottom: .5rem;
background-color: #e6e6e6;
color: rgba(0, 0, 0, .8);
border-radius: .25rem;
border-style: none;
border-width: 0;
cursor: pointer;
-webkit-appearance: button;
text-transform: none;
overflow: visible;
line-height: 1.15;
margin: 0;
will-change: transform;
-moz-osx-font-smoothing: grayscale;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform: translateZ(0);
transform: translateZ(0);
transition: -webkit-transform .25s ease-out;
transition: transform .25s ease-out;
transition: transform .25s ease-out, -webkit-transform .25s ease-out;
}
.modal__btn:focus,
.modal__btn:hover {
-webkit-transform: scale(1.05);
transform: scale(1.05);
}
.modal__btn-primary {
background-color: #00449e;
color: #fff;
}
/**************************\
Demo Animation Style
\**************************/
@keyframes mmfadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes mmfadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes mmslideIn {
from {
transform: translateY(15%);
}
to {
transform: translateY(0);
}
}
@keyframes mmslideOut {
from {
transform: translateY(0);
}
to {
transform: translateY(-10%);
}
}
.micromodal-slide {
display: none;
}
.micromodal-slide.is-open {
display: block;
}
.micromodal-slide[aria-hidden="false"] .modal__overlay {
animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="false"] .modal__container {
animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__overlay {
animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__container {
animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide .modal__container,
.micromodal-slide .modal__overlay {
will-change: transform;
}
.micromodal-slide .title {
font-size: 1.5625rem;
color: #111111;
border: none;
font-weight: bold;
text-transform: uppercase;
margin: 0 auto;
padding: 0;
}
.micromodal-slide .images {
margin: .5rem auto 1.5rem;
width: 100%;
height: 350px;
overflow: hidden;
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-ms-border-radius: 10px;
-o-border-radius: 10px;
}
.micromodal-slide .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.micromodal-slide h2.title {
font-size: 1.25rem;
margin: 0 auto .5rem;
}
.micromodal-slide h4.title {
font-size: 1rem;
margin: 0 auto .5rem;
}
.micromodal-slide .short_description {
font-size: 1.125rem;
font-weight: lighter;
}
.micromodal-slide .long_description {
font-size: 1rem;
margin-bottom: 1rem;
}
.micromodal-slide table.period {
border: none;
border-collapse: collapse;
width: 100%;
}
.micromodal-slide table.period td strong {
display: block;
font-size: .8125rem;
}
.micromodal-slide table.period tr {
border-top: 1px solid #dddddd;
}
.micromodal-slide table.period tr.noborder {
border: none;
}
.micromodal-slide table.period td {
border: none;
font-size: 1rem;
vertical-align: top;
padding: 1rem .5rem;
}
.micromodal-slide table.period tr.noborder td {
padding: 0 .5rem 1rem;
}
.micromodal-slide span.tag {
font-size: .8125rem;
padding: 5px 8px;
color: #ffffff;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
.micromodal-slide span.tag.canceled {
background-color: #FF0000;
}
.micromodal-slide span.tag.free-entry {
background-color: #28A745;
}
.micromodal-slide span.tag.limited {
background-color: #1668B2;
}
.micromodal-slide .tags {
margin-right: 1rem;
background-color: #f2f2f2;
font-size: .8125rem;
padding: 5px 8px;
color: #111111;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
+1
View File
@@ -0,0 +1 @@
.elementor-widget-divider{--divider-border-style:none;--divider-border-width:1px;--divider-color:#0c0d0e;--divider-icon-size:20px;--divider-element-spacing:10px;--divider-pattern-height:24px;--divider-pattern-size:20px;--divider-pattern-url:none;--divider-pattern-repeat:repeat-x}.elementor-widget-divider .elementor-divider{display:flex}.elementor-widget-divider .elementor-divider__text{font-size:15px;line-height:1;max-width:95%}.elementor-widget-divider .elementor-divider__element{flex-shrink:0;margin:0 var(--divider-element-spacing)}.elementor-widget-divider .elementor-icon{font-size:var(--divider-icon-size)}.elementor-widget-divider .elementor-divider-separator{direction:ltr;display:flex;margin:0}.elementor-widget-divider--view-line_icon .elementor-divider-separator,.elementor-widget-divider--view-line_text .elementor-divider-separator{align-items:center}.elementor-widget-divider--view-line_icon .elementor-divider-separator:after,.elementor-widget-divider--view-line_icon .elementor-divider-separator:before,.elementor-widget-divider--view-line_text .elementor-divider-separator:after,.elementor-widget-divider--view-line_text .elementor-divider-separator:before{border-block-end:0;border-block-start:var(--divider-border-width) var(--divider-border-style) var(--divider-color);content:"";display:block;flex-grow:1}.elementor-widget-divider--element-align-left .elementor-divider .elementor-divider-separator>.elementor-divider__svg:first-of-type{flex-grow:0;flex-shrink:100}.elementor-widget-divider--element-align-left .elementor-divider-separator:before{content:none}.elementor-widget-divider--element-align-left .elementor-divider__element{margin-left:0}.elementor-widget-divider--element-align-right .elementor-divider .elementor-divider-separator>.elementor-divider__svg:last-of-type{flex-grow:0;flex-shrink:100}.elementor-widget-divider--element-align-right .elementor-divider-separator:after{content:none}.elementor-widget-divider--element-align-right .elementor-divider__element{margin-right:0}.elementor-widget-divider--element-align-start .elementor-divider .elementor-divider-separator>.elementor-divider__svg:first-of-type{flex-grow:0;flex-shrink:100}.elementor-widget-divider--element-align-start .elementor-divider-separator:before{content:none}.elementor-widget-divider--element-align-start .elementor-divider__element{margin-inline-start:0}.elementor-widget-divider--element-align-end .elementor-divider .elementor-divider-separator>.elementor-divider__svg:last-of-type{flex-grow:0;flex-shrink:100}.elementor-widget-divider--element-align-end .elementor-divider-separator:after{content:none}.elementor-widget-divider--element-align-end .elementor-divider__element{margin-inline-end:0}.elementor-widget-divider:not(.elementor-widget-divider--view-line_text):not(.elementor-widget-divider--view-line_icon) .elementor-divider-separator{border-block-start:var(--divider-border-width) var(--divider-border-style) var(--divider-color)}.elementor-widget-divider--separator-type-pattern{--divider-border-style:none}.elementor-widget-divider--separator-type-pattern.elementor-widget-divider--view-line .elementor-divider-separator,.elementor-widget-divider--separator-type-pattern:not(.elementor-widget-divider--view-line) .elementor-divider-separator:after,.elementor-widget-divider--separator-type-pattern:not(.elementor-widget-divider--view-line) .elementor-divider-separator:before,.elementor-widget-divider--separator-type-pattern:not([class*=elementor-widget-divider--view]) .elementor-divider-separator{background-color:var(--divider-color);-webkit-mask-image:var(--divider-pattern-url);mask-image:var(--divider-pattern-url);-webkit-mask-repeat:var(--divider-pattern-repeat);mask-repeat:var(--divider-pattern-repeat);-webkit-mask-size:var(--divider-pattern-size) 100%;mask-size:var(--divider-pattern-size) 100%;min-height:var(--divider-pattern-height);width:100%}.elementor-widget-divider--no-spacing{--divider-pattern-size:auto}.elementor-widget-divider--bg-round{--divider-pattern-repeat:round}.rtl .elementor-widget-divider .elementor-divider__text{direction:rtl}.e-con-inner>.elementor-widget-divider,.e-con>.elementor-widget-divider{width:var(--container-widget-width,100%);--flex-grow:var( --container-widget-flex-grow )}
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
+434
View File
@@ -0,0 +1,434 @@
.events-list {
list-style: none;
margin: 0 auto;
padding: 0;
}
.events-list .event {
border-bottom: 1px solid #5e5e5e;
margin-bottom: 1rem;
padding: 1rem 0;
display: flex;
gap: 20px;
}
.events-list .event .date {
padding: 1rem 3rem;
}
.events-list .event .date .day,
.events-list .event .date .month {
display: block;
text-align: center;
}
.events-list .event .date .day {
font-size: 2.1875rem;
font-weight: bold;
}
.events-list .event .date .month {}
.events-list .event .title {
margin: 0 auto;
padding: 0;
}
.events-list .event .description {
margin: .5rem auto;
padding: 0;
line-height: 1.3;
}
.events-list .event .images {
width: 200px;
height: 200px;
}
.events-list .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-list .event .event-info {
display: flex;
gap: 20px;
}
.events-grid .event {
float: left;
width: 31.33%;
margin: 1% 0 1% 3%;
border: none !important;
display: block;
}
.events-grid .event:after {
clear: both;
}
.events-grid .event:first-child {
margin-left: 0;
}
.events-grid .event .images {
display: block;
width: 100%;
height: 300px;
margin-bottom: 1rem;
position: relative;
}
.events-grid .event .images .date {
position: absolute;
left: 0;
bottom: 0;
color: #ffffff;
background-color: red;
padding: .8rem 1.5rem;
}
.events-grid .event .date .day {
font-size: 1.875rem;
font-weight: bold;
display: block;
text-align: center;
}
.events-grid .event .date .month {
display: block;
text-align: center;
}
.events-grid .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-grid .event .event-content .title {
font-size: 2rem;
line-height: 1.3;
margin: 0 auto 1rem;
}
.events-grid .event .event-content p {
font-size: .875rem;
line-height: 1.5;
}
.events-grid .event .buttons .btn-cta {
background-color: red;
color: #ffffff;
font-size: 1rem;
padding: .8rem 1.5rem;
cursor: pointer;
}
@media only screen and (max-width: 700px) {
.events-grid .event {
width: 48.5%;
margin: 1% 0 1% 3%;
}
}
@media only screen and (max-width: 480px) {
.events-grid .event {
float: none;
width: 100%;
margin: 0 auto 3%;
}
}
/**************************\
Basic Modal Styles
\**************************/
.modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: 99999;
}
.modal__container {
background-color: #fff;
padding: 30px;
width: 900px;
max-width: 90%;
max-height: 100vh;
border-radius: 4px;
overflow-y: auto;
box-sizing: border-box;
}
.modal__header {
display: flex;
justify-content: space-between;
align-items: center;
}
.modal__title {
margin-top: 0;
margin-bottom: 0;
font-weight: 600;
font-size: 1.25rem;
line-height: 1.25;
color: #000000;
box-sizing: border-box;
}
.modal__close {
background: transparent;
border: 0;
}
.modal__header .modal__close:before {
content: "\2715";
}
.modal__content {
margin-top: 2rem;
margin-bottom: 2rem;
line-height: 1.5;
color: rgba(0, 0, 0, .8);
}
.modal__btn {
font-size: .875rem;
padding-left: 1rem;
padding-right: 1rem;
padding-top: .5rem;
padding-bottom: .5rem;
background-color: #e6e6e6;
color: rgba(0, 0, 0, .8);
border-radius: .25rem;
border-style: none;
border-width: 0;
cursor: pointer;
-webkit-appearance: button;
text-transform: none;
overflow: visible;
line-height: 1.15;
margin: 0;
will-change: transform;
-moz-osx-font-smoothing: grayscale;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform: translateZ(0);
transform: translateZ(0);
transition: -webkit-transform .25s ease-out;
transition: transform .25s ease-out;
transition: transform .25s ease-out, -webkit-transform .25s ease-out;
}
.modal__btn:focus,
.modal__btn:hover {
-webkit-transform: scale(1.05);
transform: scale(1.05);
}
.modal__btn-primary {
background-color: #00449e;
color: #fff;
}
/**************************\
Demo Animation Style
\**************************/
@keyframes mmfadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes mmfadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes mmslideIn {
from {
transform: translateY(15%);
}
to {
transform: translateY(0);
}
}
@keyframes mmslideOut {
from {
transform: translateY(0);
}
to {
transform: translateY(-10%);
}
}
.micromodal-slide {
display: none;
}
.micromodal-slide.is-open {
display: block;
}
.micromodal-slide[aria-hidden="false"] .modal__overlay {
animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="false"] .modal__container {
animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__overlay {
animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__container {
animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide .modal__container,
.micromodal-slide .modal__overlay {
will-change: transform;
}
.micromodal-slide .title {
font-size: 1.5625rem;
color: #111111;
border: none;
font-weight: bold;
text-transform: uppercase;
margin: 0 auto;
padding: 0;
}
.micromodal-slide .images {
margin: .5rem auto 1.5rem;
width: 100%;
height: 350px;
overflow: hidden;
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-ms-border-radius: 10px;
-o-border-radius: 10px;
}
.micromodal-slide .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.micromodal-slide h2.title {
font-size: 1.25rem;
margin: 0 auto .5rem;
}
.micromodal-slide h4.title {
font-size: 1rem;
margin: 0 auto .5rem;
}
.micromodal-slide .short_description {
font-size: 1.125rem;
font-weight: lighter;
}
.micromodal-slide .long_description {
font-size: 1rem;
margin-bottom: 1rem;
}
.micromodal-slide table.period {
border: none;
border-collapse: collapse;
width: 100%;
}
.micromodal-slide table.period td strong {
display: block;
font-size: .8125rem;
}
.micromodal-slide table.period tr {
border-top: 1px solid #dddddd;
}
.micromodal-slide table.period tr.noborder {
border: none;
}
.micromodal-slide table.period td {
border: none;
font-size: 1rem;
vertical-align: top;
padding: 1rem .5rem;
}
.micromodal-slide table.period tr.noborder td {
padding: 0 .5rem 1rem;
}
.micromodal-slide span.tag {
font-size: .8125rem;
padding: 5px 8px;
color: #ffffff;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
.micromodal-slide span.tag.canceled {
background-color: #FF0000;
}
.micromodal-slide span.tag.free-entry {
background-color: #28A745;
}
.micromodal-slide span.tag.limited {
background-color: #1668B2;
}
.micromodal-slide .tags {
margin-right: 1rem;
background-color: #f2f2f2;
font-size: .8125rem;
padding: 5px 8px;
color: #111111;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
Binary file not shown.
+434
View File
@@ -0,0 +1,434 @@
.events-list {
list-style: none;
margin: 0 auto;
padding: 0;
}
.events-list .event {
border-bottom: 1px solid #5e5e5e;
margin-bottom: 1rem;
padding: 1rem 0;
display: flex;
gap: 20px;
}
.events-list .event .date {
padding: 1rem 3rem;
}
.events-list .event .date .day,
.events-list .event .date .month {
display: block;
text-align: center;
}
.events-list .event .date .day {
font-size: 2.1875rem;
font-weight: bold;
}
.events-list .event .date .month {}
.events-list .event .title {
margin: 0 auto;
padding: 0;
}
.events-list .event .description {
margin: .5rem auto;
padding: 0;
line-height: 1.3;
}
.events-list .event .images {
width: 200px;
height: 200px;
}
.events-list .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-list .event .event-info {
display: flex;
gap: 20px;
}
.events-grid .event {
float: left;
width: 31.33%;
margin: 1% 0 1% 3%;
border: none !important;
display: block;
}
.events-grid .event:after {
clear: both;
}
.events-grid .event:first-child {
margin-left: 0;
}
.events-grid .event .images {
display: block;
width: 100%;
height: 300px;
margin-bottom: 1rem;
position: relative;
}
.events-grid .event .images .date {
position: absolute;
left: 0;
bottom: 0;
color: #ffffff;
background-color: red;
padding: .8rem 1.5rem;
}
.events-grid .event .date .day {
font-size: 1.875rem;
font-weight: bold;
display: block;
text-align: center;
}
.events-grid .event .date .month {
display: block;
text-align: center;
}
.events-grid .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-grid .event .event-content .title {
font-size: 2rem;
line-height: 1.3;
margin: 0 auto 1rem;
}
.events-grid .event .event-content p {
font-size: .875rem;
line-height: 1.5;
}
.events-grid .event .buttons .btn-cta {
background-color: red;
color: #ffffff;
font-size: 1rem;
padding: .8rem 1.5rem;
cursor: pointer;
}
@media only screen and (max-width: 700px) {
.events-grid .event {
width: 48.5%;
margin: 1% 0 1% 3%;
}
}
@media only screen and (max-width: 480px) {
.events-grid .event {
float: none;
width: 100%;
margin: 0 auto 3%;
}
}
/**************************\
Basic Modal Styles
\**************************/
.modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: 99999;
}
.modal__container {
background-color: #fff;
padding: 30px;
width: 900px;
max-width: 90%;
max-height: 100vh;
border-radius: 4px;
overflow-y: auto;
box-sizing: border-box;
}
.modal__header {
display: flex;
justify-content: space-between;
align-items: center;
}
.modal__title {
margin-top: 0;
margin-bottom: 0;
font-weight: 600;
font-size: 1.25rem;
line-height: 1.25;
color: #000000;
box-sizing: border-box;
}
.modal__close {
background: transparent;
border: 0;
}
.modal__header .modal__close:before {
content: "\2715";
}
.modal__content {
margin-top: 2rem;
margin-bottom: 2rem;
line-height: 1.5;
color: rgba(0, 0, 0, .8);
}
.modal__btn {
font-size: .875rem;
padding-left: 1rem;
padding-right: 1rem;
padding-top: .5rem;
padding-bottom: .5rem;
background-color: #e6e6e6;
color: rgba(0, 0, 0, .8);
border-radius: .25rem;
border-style: none;
border-width: 0;
cursor: pointer;
-webkit-appearance: button;
text-transform: none;
overflow: visible;
line-height: 1.15;
margin: 0;
will-change: transform;
-moz-osx-font-smoothing: grayscale;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform: translateZ(0);
transform: translateZ(0);
transition: -webkit-transform .25s ease-out;
transition: transform .25s ease-out;
transition: transform .25s ease-out, -webkit-transform .25s ease-out;
}
.modal__btn:focus,
.modal__btn:hover {
-webkit-transform: scale(1.05);
transform: scale(1.05);
}
.modal__btn-primary {
background-color: #00449e;
color: #fff;
}
/**************************\
Demo Animation Style
\**************************/
@keyframes mmfadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes mmfadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes mmslideIn {
from {
transform: translateY(15%);
}
to {
transform: translateY(0);
}
}
@keyframes mmslideOut {
from {
transform: translateY(0);
}
to {
transform: translateY(-10%);
}
}
.micromodal-slide {
display: none;
}
.micromodal-slide.is-open {
display: block;
}
.micromodal-slide[aria-hidden="false"] .modal__overlay {
animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="false"] .modal__container {
animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__overlay {
animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__container {
animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide .modal__container,
.micromodal-slide .modal__overlay {
will-change: transform;
}
.micromodal-slide .title {
font-size: 1.5625rem;
color: #111111;
border: none;
font-weight: bold;
text-transform: uppercase;
margin: 0 auto;
padding: 0;
}
.micromodal-slide .images {
margin: .5rem auto 1.5rem;
width: 100%;
height: 350px;
overflow: hidden;
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-ms-border-radius: 10px;
-o-border-radius: 10px;
}
.micromodal-slide .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.micromodal-slide h2.title {
font-size: 1.25rem;
margin: 0 auto .5rem;
}
.micromodal-slide h4.title {
font-size: 1rem;
margin: 0 auto .5rem;
}
.micromodal-slide .short_description {
font-size: 1.125rem;
font-weight: lighter;
}
.micromodal-slide .long_description {
font-size: 1rem;
margin-bottom: 1rem;
}
.micromodal-slide table.period {
border: none;
border-collapse: collapse;
width: 100%;
}
.micromodal-slide table.period td strong {
display: block;
font-size: .8125rem;
}
.micromodal-slide table.period tr {
border-top: 1px solid #dddddd;
}
.micromodal-slide table.period tr.noborder {
border: none;
}
.micromodal-slide table.period td {
border: none;
font-size: 1rem;
vertical-align: top;
padding: 1rem .5rem;
}
.micromodal-slide table.period tr.noborder td {
padding: 0 .5rem 1rem;
}
.micromodal-slide span.tag {
font-size: .8125rem;
padding: 5px 8px;
color: #ffffff;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
.micromodal-slide span.tag.canceled {
background-color: #FF0000;
}
.micromodal-slide span.tag.free-entry {
background-color: #28A745;
}
.micromodal-slide span.tag.limited {
background-color: #1668B2;
}
.micromodal-slide .tags {
margin-right: 1rem;
background-color: #f2f2f2;
font-size: .8125rem;
padding: 5px 8px;
color: #111111;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
+434
View File
@@ -0,0 +1,434 @@
.events-list {
list-style: none;
margin: 0 auto;
padding: 0;
}
.events-list .event {
border-bottom: 1px solid #5e5e5e;
margin-bottom: 1rem;
padding: 1rem 0;
display: flex;
gap: 20px;
}
.events-list .event .date {
padding: 1rem 3rem;
}
.events-list .event .date .day,
.events-list .event .date .month {
display: block;
text-align: center;
}
.events-list .event .date .day {
font-size: 2.1875rem;
font-weight: bold;
}
.events-list .event .date .month {}
.events-list .event .title {
margin: 0 auto;
padding: 0;
}
.events-list .event .description {
margin: .5rem auto;
padding: 0;
line-height: 1.3;
}
.events-list .event .images {
width: 200px;
height: 200px;
}
.events-list .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-list .event .event-info {
display: flex;
gap: 20px;
}
.events-grid .event {
float: left;
width: 31.33%;
margin: 1% 0 1% 3%;
border: none !important;
display: block;
}
.events-grid .event:after {
clear: both;
}
.events-grid .event:first-child {
margin-left: 0;
}
.events-grid .event .images {
display: block;
width: 100%;
height: 300px;
margin-bottom: 1rem;
position: relative;
}
.events-grid .event .images .date {
position: absolute;
left: 0;
bottom: 0;
color: #ffffff;
background-color: red;
padding: .8rem 1.5rem;
}
.events-grid .event .date .day {
font-size: 1.875rem;
font-weight: bold;
display: block;
text-align: center;
}
.events-grid .event .date .month {
display: block;
text-align: center;
}
.events-grid .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-grid .event .event-content .title {
font-size: 2rem;
line-height: 1.3;
margin: 0 auto 1rem;
}
.events-grid .event .event-content p {
font-size: .875rem;
line-height: 1.5;
}
.events-grid .event .buttons .btn-cta {
background-color: red;
color: #ffffff;
font-size: 1rem;
padding: .8rem 1.5rem;
cursor: pointer;
}
@media only screen and (max-width: 700px) {
.events-grid .event {
width: 48.5%;
margin: 1% 0 1% 3%;
}
}
@media only screen and (max-width: 480px) {
.events-grid .event {
float: none;
width: 100%;
margin: 0 auto 3%;
}
}
/**************************\
Basic Modal Styles
\**************************/
.modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: 99999;
}
.modal__container {
background-color: #fff;
padding: 30px;
width: 900px;
max-width: 90%;
max-height: 100vh;
border-radius: 4px;
overflow-y: auto;
box-sizing: border-box;
}
.modal__header {
display: flex;
justify-content: space-between;
align-items: center;
}
.modal__title {
margin-top: 0;
margin-bottom: 0;
font-weight: 600;
font-size: 1.25rem;
line-height: 1.25;
color: #000000;
box-sizing: border-box;
}
.modal__close {
background: transparent;
border: 0;
}
.modal__header .modal__close:before {
content: "\2715";
}
.modal__content {
margin-top: 2rem;
margin-bottom: 2rem;
line-height: 1.5;
color: rgba(0, 0, 0, .8);
}
.modal__btn {
font-size: .875rem;
padding-left: 1rem;
padding-right: 1rem;
padding-top: .5rem;
padding-bottom: .5rem;
background-color: #e6e6e6;
color: rgba(0, 0, 0, .8);
border-radius: .25rem;
border-style: none;
border-width: 0;
cursor: pointer;
-webkit-appearance: button;
text-transform: none;
overflow: visible;
line-height: 1.15;
margin: 0;
will-change: transform;
-moz-osx-font-smoothing: grayscale;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform: translateZ(0);
transform: translateZ(0);
transition: -webkit-transform .25s ease-out;
transition: transform .25s ease-out;
transition: transform .25s ease-out, -webkit-transform .25s ease-out;
}
.modal__btn:focus,
.modal__btn:hover {
-webkit-transform: scale(1.05);
transform: scale(1.05);
}
.modal__btn-primary {
background-color: #00449e;
color: #fff;
}
/**************************\
Demo Animation Style
\**************************/
@keyframes mmfadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes mmfadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes mmslideIn {
from {
transform: translateY(15%);
}
to {
transform: translateY(0);
}
}
@keyframes mmslideOut {
from {
transform: translateY(0);
}
to {
transform: translateY(-10%);
}
}
.micromodal-slide {
display: none;
}
.micromodal-slide.is-open {
display: block;
}
.micromodal-slide[aria-hidden="false"] .modal__overlay {
animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="false"] .modal__container {
animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__overlay {
animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__container {
animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide .modal__container,
.micromodal-slide .modal__overlay {
will-change: transform;
}
.micromodal-slide .title {
font-size: 1.5625rem;
color: #111111;
border: none;
font-weight: bold;
text-transform: uppercase;
margin: 0 auto;
padding: 0;
}
.micromodal-slide .images {
margin: .5rem auto 1.5rem;
width: 100%;
height: 350px;
overflow: hidden;
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-ms-border-radius: 10px;
-o-border-radius: 10px;
}
.micromodal-slide .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.micromodal-slide h2.title {
font-size: 1.25rem;
margin: 0 auto .5rem;
}
.micromodal-slide h4.title {
font-size: 1rem;
margin: 0 auto .5rem;
}
.micromodal-slide .short_description {
font-size: 1.125rem;
font-weight: lighter;
}
.micromodal-slide .long_description {
font-size: 1rem;
margin-bottom: 1rem;
}
.micromodal-slide table.period {
border: none;
border-collapse: collapse;
width: 100%;
}
.micromodal-slide table.period td strong {
display: block;
font-size: .8125rem;
}
.micromodal-slide table.period tr {
border-top: 1px solid #dddddd;
}
.micromodal-slide table.period tr.noborder {
border: none;
}
.micromodal-slide table.period td {
border: none;
font-size: 1rem;
vertical-align: top;
padding: 1rem .5rem;
}
.micromodal-slide table.period tr.noborder td {
padding: 0 .5rem 1rem;
}
.micromodal-slide span.tag {
font-size: .8125rem;
padding: 5px 8px;
color: #ffffff;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
.micromodal-slide span.tag.canceled {
background-color: #FF0000;
}
.micromodal-slide span.tag.free-entry {
background-color: #28A745;
}
.micromodal-slide span.tag.limited {
background-color: #1668B2;
}
.micromodal-slide .tags {
margin-right: 1rem;
background-color: #f2f2f2;
font-size: .8125rem;
padding: 5px 8px;
color: #111111;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
+1
View File
@@ -0,0 +1 @@
{"version":"1.0","provider_name":"Rijecki sportski savez","provider_url":"https:\/\/rss.hr","author_name":"RSS","author_url":"https:\/\/rss.hr\/author\/sasa\/","title":"SONKEI Respect in Sport, Respect in Life","type":"rich","width":600,"height":338,"html":"<blockquote class=\"wp-embedded-content\" data-secret=\"w59akSKiNc\"><a href=\"https:\/\/rss.hr\/sonkei-respect-in-sport-respect-in-life\/\">SONKEI Respect in Sport, Respect in Life<\/a><\/blockquote><iframe sandbox=\"allow-scripts\" security=\"restricted\" src=\"https:\/\/rss.hr\/sonkei-respect-in-sport-respect-in-life\/embed\/#?secret=w59akSKiNc\" width=\"600\" height=\"338\" title=\"&#8220;SONKEI Respect in Sport, Respect in Life&#8221; &#8212; Rijecki sportski savez\" data-secret=\"w59akSKiNc\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\" class=\"wp-embedded-content\"><\/iframe><script>\n\/*! This file is auto-generated *\/\n!function(d,l){\"use strict\";l.querySelector&&d.addEventListener&&\"undefined\"!=typeof URL&&(d.wp=d.wp||{},d.wp.receiveEmbedMessage||(d.wp.receiveEmbedMessage=function(e){var t=e.data;if((t||t.secret||t.message||t.value)&&!\/[^a-zA-Z0-9]\/.test(t.secret)){for(var s,r,n,a=l.querySelectorAll('iframe[data-secret=\"'+t.secret+'\"]'),o=l.querySelectorAll('blockquote[data-secret=\"'+t.secret+'\"]'),c=new RegExp(\"^https?:$\",\"i\"),i=0;i<o.length;i++)o[i].style.display=\"none\";for(i=0;i<a.length;i++)s=a[i],e.source===s.contentWindow&&(s.removeAttribute(\"style\"),\"height\"===t.message?(1e3<(r=parseInt(t.value,10))?r=1e3:~~r<200&&(r=200),s.height=r):\"link\"===t.message&&(r=new URL(s.getAttribute(\"src\")),n=new URL(t.value),c.test(n.protocol))&&n.host===r.host&&l.activeElement===s&&(d.top.location.href=t.value))}},d.addEventListener(\"message\",d.wp.receiveEmbedMessage,!1),l.addEventListener(\"DOMContentLoaded\",function(){for(var e,t,s=l.querySelectorAll(\"iframe.wp-embedded-content\"),r=0;r<s.length;r++)(t=(e=s[r]).getAttribute(\"data-secret\"))||(t=Math.random().toString(36).substring(2,12),e.src+=\"#?secret=\"+t,e.setAttribute(\"data-secret\",t)),e.contentWindow.postMessage({message:\"ready\",secret:t},\"*\")},!1)))}(window,document);\n\/\/# sourceURL=https:\/\/rss.hr\/wp-includes\/js\/wp-embed.min.js\n<\/script>\n"}
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
.elementor-8321 .elementor-element.elementor-element-5a7d978d:not(.elementor-motion-effects-element-type-background), .elementor-8321 .elementor-element.elementor-element-5a7d978d > .elementor-motion-effects-container > .elementor-motion-effects-layer{background-color:#FFFFFF;background-image:url("https://rss.hr/wp-content/uploads/2022/01/flag-eu-europe-1463476.jpg");background-position:center center;background-size:cover;}.elementor-8321 .elementor-element.elementor-element-5a7d978d > .elementor-background-overlay{background-color:transparent;background-image:linear-gradient(180deg, #153243 0%, #192D31 100%);opacity:0.7;transition:background 0.3s, border-radius 0.3s, opacity 0.3s;}.elementor-8321 .elementor-element.elementor-element-5a7d978d > .elementor-container{min-height:300px;}.elementor-8321 .elementor-element.elementor-element-5a7d978d{transition:background 0.3s, border 0.3s, border-radius 0.3s, box-shadow 0.3s;padding:200px 0px 100px 0px;}.elementor-8321 .elementor-element.elementor-element-36e3182{text-align:center;}.elementor-8321 .elementor-element.elementor-element-36e3182 .elementor-heading-title{letter-spacing:2.5px;color:#FFFFFF;}.elementor-8321 .elementor-element.elementor-element-37512b3 > .elementor-widget-container{padding:15px 15px 15px 15px;}.elementor-8321 .elementor-element.elementor-element-37512b3{text-align:center;}.elementor-8321 .elementor-element.elementor-element-37512b3 img{width:100%;max-width:100%;height:192px;object-fit:contain;object-position:center center;}.elementor-8321 .elementor-element.elementor-element-f9184cf{text-align:start;}.elementor-8321 .elementor-element.elementor-element-6f65952{margin-top:0px;margin-bottom:0px;padding:0px 0px 0px 0px;}.elementor-8321 .elementor-element.elementor-element-968aaf0 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-8321 .elementor-element.elementor-element-968aaf0{text-align:center;}.elementor-8321 .elementor-element.elementor-element-968aaf0 img{width:40%;max-width:40%;height:155px;object-fit:contain;object-position:center center;border-radius:0px 0px 0px 0px;}.elementor-8321 .elementor-element.elementor-element-23957eb{--spacer-size:10px;}body.elementor-page-8321:not(.elementor-motion-effects-element-type-background), body.elementor-page-8321 > .elementor-motion-effects-container > .elementor-motion-effects-layer{background-color:#ffffff;}@media(min-width:1025px){.elementor-8321 .elementor-element.elementor-element-5a7d978d:not(.elementor-motion-effects-element-type-background), .elementor-8321 .elementor-element.elementor-element-5a7d978d > .elementor-motion-effects-container > .elementor-motion-effects-layer{background-attachment:fixed;}}@media(max-width:1024px){.elementor-8321 .elementor-element.elementor-element-5a7d978d{padding:140px 80px 80px 80px;}.elementor-8321 .elementor-element.elementor-element-36e3182 .elementor-heading-title{font-size:40px;}.elementor-8321 .elementor-element.elementor-element-37512b3 img{width:60%;max-width:60%;}.elementor-8321 .elementor-element.elementor-element-968aaf0 img{width:50%;max-width:50%;}}@media(max-width:767px){.elementor-8321 .elementor-element.elementor-element-5a7d978d{padding:0px 0px 0px 0px;}.elementor-8321 .elementor-element.elementor-element-7b7c0473{width:100%;}.elementor-8321 .elementor-element.elementor-element-36e3182 .elementor-heading-title{font-size:30px;}.elementor-8321 .elementor-element.elementor-element-37512b3{text-align:center;}.elementor-8321 .elementor-element.elementor-element-37512b3 img{width:70%;max-width:70%;}.elementor-8321 .elementor-element.elementor-element-968aaf0{text-align:center;}.elementor-8321 .elementor-element.elementor-element-968aaf0 img{width:70%;max-width:70%;height:61px;}}
+434
View File
@@ -0,0 +1,434 @@
.events-list {
list-style: none;
margin: 0 auto;
padding: 0;
}
.events-list .event {
border-bottom: 1px solid #5e5e5e;
margin-bottom: 1rem;
padding: 1rem 0;
display: flex;
gap: 20px;
}
.events-list .event .date {
padding: 1rem 3rem;
}
.events-list .event .date .day,
.events-list .event .date .month {
display: block;
text-align: center;
}
.events-list .event .date .day {
font-size: 2.1875rem;
font-weight: bold;
}
.events-list .event .date .month {}
.events-list .event .title {
margin: 0 auto;
padding: 0;
}
.events-list .event .description {
margin: .5rem auto;
padding: 0;
line-height: 1.3;
}
.events-list .event .images {
width: 200px;
height: 200px;
}
.events-list .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-list .event .event-info {
display: flex;
gap: 20px;
}
.events-grid .event {
float: left;
width: 31.33%;
margin: 1% 0 1% 3%;
border: none !important;
display: block;
}
.events-grid .event:after {
clear: both;
}
.events-grid .event:first-child {
margin-left: 0;
}
.events-grid .event .images {
display: block;
width: 100%;
height: 300px;
margin-bottom: 1rem;
position: relative;
}
.events-grid .event .images .date {
position: absolute;
left: 0;
bottom: 0;
color: #ffffff;
background-color: red;
padding: .8rem 1.5rem;
}
.events-grid .event .date .day {
font-size: 1.875rem;
font-weight: bold;
display: block;
text-align: center;
}
.events-grid .event .date .month {
display: block;
text-align: center;
}
.events-grid .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-grid .event .event-content .title {
font-size: 2rem;
line-height: 1.3;
margin: 0 auto 1rem;
}
.events-grid .event .event-content p {
font-size: .875rem;
line-height: 1.5;
}
.events-grid .event .buttons .btn-cta {
background-color: red;
color: #ffffff;
font-size: 1rem;
padding: .8rem 1.5rem;
cursor: pointer;
}
@media only screen and (max-width: 700px) {
.events-grid .event {
width: 48.5%;
margin: 1% 0 1% 3%;
}
}
@media only screen and (max-width: 480px) {
.events-grid .event {
float: none;
width: 100%;
margin: 0 auto 3%;
}
}
/**************************\
Basic Modal Styles
\**************************/
.modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: 99999;
}
.modal__container {
background-color: #fff;
padding: 30px;
width: 900px;
max-width: 90%;
max-height: 100vh;
border-radius: 4px;
overflow-y: auto;
box-sizing: border-box;
}
.modal__header {
display: flex;
justify-content: space-between;
align-items: center;
}
.modal__title {
margin-top: 0;
margin-bottom: 0;
font-weight: 600;
font-size: 1.25rem;
line-height: 1.25;
color: #000000;
box-sizing: border-box;
}
.modal__close {
background: transparent;
border: 0;
}
.modal__header .modal__close:before {
content: "\2715";
}
.modal__content {
margin-top: 2rem;
margin-bottom: 2rem;
line-height: 1.5;
color: rgba(0, 0, 0, .8);
}
.modal__btn {
font-size: .875rem;
padding-left: 1rem;
padding-right: 1rem;
padding-top: .5rem;
padding-bottom: .5rem;
background-color: #e6e6e6;
color: rgba(0, 0, 0, .8);
border-radius: .25rem;
border-style: none;
border-width: 0;
cursor: pointer;
-webkit-appearance: button;
text-transform: none;
overflow: visible;
line-height: 1.15;
margin: 0;
will-change: transform;
-moz-osx-font-smoothing: grayscale;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform: translateZ(0);
transform: translateZ(0);
transition: -webkit-transform .25s ease-out;
transition: transform .25s ease-out;
transition: transform .25s ease-out, -webkit-transform .25s ease-out;
}
.modal__btn:focus,
.modal__btn:hover {
-webkit-transform: scale(1.05);
transform: scale(1.05);
}
.modal__btn-primary {
background-color: #00449e;
color: #fff;
}
/**************************\
Demo Animation Style
\**************************/
@keyframes mmfadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes mmfadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes mmslideIn {
from {
transform: translateY(15%);
}
to {
transform: translateY(0);
}
}
@keyframes mmslideOut {
from {
transform: translateY(0);
}
to {
transform: translateY(-10%);
}
}
.micromodal-slide {
display: none;
}
.micromodal-slide.is-open {
display: block;
}
.micromodal-slide[aria-hidden="false"] .modal__overlay {
animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="false"] .modal__container {
animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__overlay {
animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__container {
animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide .modal__container,
.micromodal-slide .modal__overlay {
will-change: transform;
}
.micromodal-slide .title {
font-size: 1.5625rem;
color: #111111;
border: none;
font-weight: bold;
text-transform: uppercase;
margin: 0 auto;
padding: 0;
}
.micromodal-slide .images {
margin: .5rem auto 1.5rem;
width: 100%;
height: 350px;
overflow: hidden;
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-ms-border-radius: 10px;
-o-border-radius: 10px;
}
.micromodal-slide .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.micromodal-slide h2.title {
font-size: 1.25rem;
margin: 0 auto .5rem;
}
.micromodal-slide h4.title {
font-size: 1rem;
margin: 0 auto .5rem;
}
.micromodal-slide .short_description {
font-size: 1.125rem;
font-weight: lighter;
}
.micromodal-slide .long_description {
font-size: 1rem;
margin-bottom: 1rem;
}
.micromodal-slide table.period {
border: none;
border-collapse: collapse;
width: 100%;
}
.micromodal-slide table.period td strong {
display: block;
font-size: .8125rem;
}
.micromodal-slide table.period tr {
border-top: 1px solid #dddddd;
}
.micromodal-slide table.period tr.noborder {
border: none;
}
.micromodal-slide table.period td {
border: none;
font-size: 1rem;
vertical-align: top;
padding: 1rem .5rem;
}
.micromodal-slide table.period tr.noborder td {
padding: 0 .5rem 1rem;
}
.micromodal-slide span.tag {
font-size: .8125rem;
padding: 5px 8px;
color: #ffffff;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
.micromodal-slide span.tag.canceled {
background-color: #FF0000;
}
.micromodal-slide span.tag.free-entry {
background-color: #28A745;
}
.micromodal-slide span.tag.limited {
background-color: #1668B2;
}
.micromodal-slide .tags {
margin-right: 1rem;
background-color: #f2f2f2;
font-size: .8125rem;
padding: 5px 8px;
color: #111111;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
+2
View File
@@ -0,0 +1,2 @@
/*! elementor-pro - v4.0.0 - 28-04-2026 */
.elementor-search-form{display:block;transition:.2s}.elementor-search-form .e-font-icon-svg-container{align-items:center;display:flex;height:100%;justify-content:center;width:100%}.elementor-search-form button,.elementor-search-form input[type=search]{-webkit-appearance:none;-moz-appearance:none;background:none;border:0;display:inline-block;font-size:15px;line-height:1;margin:0;min-width:0;padding:0;vertical-align:middle;white-space:normal}.elementor-search-form button:focus,.elementor-search-form input[type=search]:focus{color:inherit;outline:0}.elementor-search-form button{background-color:#69727d;border-radius:0;color:#fff;font-size:var(--e-search-form-submit-icon-size,16px)}.elementor-search-form__container{border:0 solid transparent;display:flex;min-height:50px;overflow:hidden;transition:.2s}.elementor-search-form__container:not(.elementor-search-form--full-screen){background:#f1f2f3}.elementor-search-form__input{color:#3f444b;flex-basis:100%;transition:color .2s}.elementor-search-form__input::-moz-placeholder{color:inherit;font-family:inherit;opacity:.6}.elementor-search-form__input::placeholder{color:inherit;font-family:inherit;opacity:.6}.elementor-search-form__submit{font-size:var(--e-search-form-submit-icon-size,16px);transition:color .2s,background .2s}.elementor-search-form__submit svg{fill:var(--e-search-form-submit-text-color,#fff);height:var(--e-search-form-submit-icon-size,16px);width:var(--e-search-form-submit-icon-size,16px)}.elementor-search-form .elementor-search-form__submit,.elementor-search-form .elementor-search-form__submit:hover{border:none;border-radius:0;color:var(--e-search-form-submit-text-color,#fff)}.elementor-search-form--button-type-text .elementor-search-form__submit{flex-shrink:0}.elementor-search-form--skin-minimal .elementor-search-form__icon{align-items:center;color:#3f444b;display:flex;fill:#3f444b;font-size:var(--e-search-form-icon-size-minimal,15px);justify-content:flex-end;opacity:.6}.elementor-search-form--skin-minimal .elementor-search-form__icon svg{height:var(--e-search-form-icon-size-minimal,15px);width:var(--e-search-form-icon-size-minimal,15px)}.elementor-search-form--skin-minimal:focus-within{outline-style:solid;outline-width:2px}.elementor-search-form--skin-full_screen .elementor-search-form input[type=search].elementor-search-form__input{border:solid #fff;border-width:0 0 1px;color:#fff;font-size:50px;line-height:1.5;text-align:center}.elementor-search-form--skin-full_screen .elementor-search-form__toggle{color:var(--e-search-form-toggle-color,#33373d);cursor:pointer;display:inline-block;font-size:var(--e-search-form-toggle-size,33px);vertical-align:middle}.elementor-search-form--skin-full_screen .elementor-search-form__toggle .e-font-icon-svg-container,.elementor-search-form--skin-full_screen .elementor-search-form__toggle i{background-color:var(--e-search-form-toggle-background-color,rgba(0,0,0,.05));border-color:var(--e-search-form-toggle-color,#33373d);border-radius:var(--e-search-form-toggle-border-radius,3px);border-style:solid;border-width:var(--e-search-form-toggle-border-width,0);display:block;height:var(--e-search-form-toggle-size,33px);position:relative;transition:.2s;width:var(--e-search-form-toggle-size,33px)}.elementor-search-form--skin-full_screen .elementor-search-form__toggle .e-font-icon-svg-container svg,.elementor-search-form--skin-full_screen .elementor-search-form__toggle .e-font-icon-svg-container:before,.elementor-search-form--skin-full_screen .elementor-search-form__toggle i svg,.elementor-search-form--skin-full_screen .elementor-search-form__toggle i:before{left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.elementor-search-form--skin-full_screen .elementor-search-form__toggle .e-font-icon-svg-container:before,.elementor-search-form--skin-full_screen .elementor-search-form__toggle i:before{font-size:var(--e-search-form-toggle-icon-size,.55em)}.elementor-search-form--skin-full_screen .elementor-search-form__toggle .e-font-icon-svg-container svg,.elementor-search-form--skin-full_screen .elementor-search-form__toggle i svg{fill:var(--e-search-form-toggle-color,#33373d);height:var(--e-search-form-toggle-icon-size,.55em);width:var(--e-search-form-toggle-icon-size,.55em)}.elementor-search-form--skin-full_screen .elementor-search-form__container{align-items:center;background-color:rgba(0,0,0,.8);height:100vh;inset:0;padding:0 15%;position:fixed;transition:.3s;z-index:9998}.elementor-search-form--skin-full_screen .elementor-search-form__container:not(.elementor-search-form--full-screen){opacity:0;overflow:hidden;transform:scale(0)}.elementor-search-form--skin-full_screen .elementor-search-form__container:not(.elementor-search-form--full-screen) .dialog-lightbox-close-button{display:none}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
.elementor-8294 .elementor-element.elementor-element-5a7d978d:not(.elementor-motion-effects-element-type-background), .elementor-8294 .elementor-element.elementor-element-5a7d978d > .elementor-motion-effects-container > .elementor-motion-effects-layer{background-color:#FFFFFF;background-image:url("https://rss.hr/wp-content/uploads/2022/01/flag-eu-europe-1463476.jpg");background-position:center center;background-size:cover;}.elementor-8294 .elementor-element.elementor-element-5a7d978d > .elementor-background-overlay{background-color:transparent;background-image:linear-gradient(180deg, #153243 0%, #192D31 100%);opacity:0.7;transition:background 0.3s, border-radius 0.3s, opacity 0.3s;}.elementor-8294 .elementor-element.elementor-element-5a7d978d > .elementor-container{min-height:300px;}.elementor-8294 .elementor-element.elementor-element-5a7d978d{transition:background 0.3s, border 0.3s, border-radius 0.3s, box-shadow 0.3s;padding:200px 0px 100px 0px;}.elementor-8294 .elementor-element.elementor-element-36e3182{text-align:center;}.elementor-8294 .elementor-element.elementor-element-36e3182 .elementor-heading-title{letter-spacing:2.5px;color:#FFFFFF;}.elementor-8294 .elementor-element.elementor-element-37512b3 > .elementor-widget-container{padding:15px 15px 15px 15px;}.elementor-8294 .elementor-element.elementor-element-37512b3{text-align:center;}.elementor-8294 .elementor-element.elementor-element-37512b3 img{width:100%;max-width:100%;height:192px;object-fit:contain;object-position:center center;}.elementor-8294 .elementor-element.elementor-element-f9184cf{text-align:start;}.elementor-8294 .elementor-element.elementor-element-6f65952{margin-top:0px;margin-bottom:0px;padding:0px 0px 0px 0px;}.elementor-8294 .elementor-element.elementor-element-968aaf0 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-8294 .elementor-element.elementor-element-968aaf0{text-align:center;}.elementor-8294 .elementor-element.elementor-element-968aaf0 img{width:40%;max-width:40%;height:155px;object-fit:contain;object-position:center center;border-radius:0px 0px 0px 0px;}.elementor-8294 .elementor-element.elementor-element-23957eb{--spacer-size:10px;}body.elementor-page-8294:not(.elementor-motion-effects-element-type-background), body.elementor-page-8294 > .elementor-motion-effects-container > .elementor-motion-effects-layer{background-color:#ffffff;}@media(min-width:1025px){.elementor-8294 .elementor-element.elementor-element-5a7d978d:not(.elementor-motion-effects-element-type-background), .elementor-8294 .elementor-element.elementor-element-5a7d978d > .elementor-motion-effects-container > .elementor-motion-effects-layer{background-attachment:fixed;}}@media(max-width:1024px){.elementor-8294 .elementor-element.elementor-element-5a7d978d{padding:140px 80px 80px 80px;}.elementor-8294 .elementor-element.elementor-element-36e3182 .elementor-heading-title{font-size:40px;}.elementor-8294 .elementor-element.elementor-element-37512b3 img{width:60%;max-width:60%;}.elementor-8294 .elementor-element.elementor-element-968aaf0 img{width:50%;max-width:50%;}}@media(max-width:767px){.elementor-8294 .elementor-element.elementor-element-5a7d978d{padding:0px 0px 0px 0px;}.elementor-8294 .elementor-element.elementor-element-7b7c0473{width:100%;}.elementor-8294 .elementor-element.elementor-element-36e3182 .elementor-heading-title{font-size:30px;}.elementor-8294 .elementor-element.elementor-element-37512b3{text-align:center;}.elementor-8294 .elementor-element.elementor-element-37512b3 img{width:70%;max-width:70%;}.elementor-8294 .elementor-element.elementor-element-968aaf0{text-align:center;}.elementor-8294 .elementor-element.elementor-element-968aaf0 img{width:70%;max-width:70%;height:61px;}}
Binary file not shown.
+434
View File
@@ -0,0 +1,434 @@
.events-list {
list-style: none;
margin: 0 auto;
padding: 0;
}
.events-list .event {
border-bottom: 1px solid #5e5e5e;
margin-bottom: 1rem;
padding: 1rem 0;
display: flex;
gap: 20px;
}
.events-list .event .date {
padding: 1rem 3rem;
}
.events-list .event .date .day,
.events-list .event .date .month {
display: block;
text-align: center;
}
.events-list .event .date .day {
font-size: 2.1875rem;
font-weight: bold;
}
.events-list .event .date .month {}
.events-list .event .title {
margin: 0 auto;
padding: 0;
}
.events-list .event .description {
margin: .5rem auto;
padding: 0;
line-height: 1.3;
}
.events-list .event .images {
width: 200px;
height: 200px;
}
.events-list .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-list .event .event-info {
display: flex;
gap: 20px;
}
.events-grid .event {
float: left;
width: 31.33%;
margin: 1% 0 1% 3%;
border: none !important;
display: block;
}
.events-grid .event:after {
clear: both;
}
.events-grid .event:first-child {
margin-left: 0;
}
.events-grid .event .images {
display: block;
width: 100%;
height: 300px;
margin-bottom: 1rem;
position: relative;
}
.events-grid .event .images .date {
position: absolute;
left: 0;
bottom: 0;
color: #ffffff;
background-color: red;
padding: .8rem 1.5rem;
}
.events-grid .event .date .day {
font-size: 1.875rem;
font-weight: bold;
display: block;
text-align: center;
}
.events-grid .event .date .month {
display: block;
text-align: center;
}
.events-grid .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-grid .event .event-content .title {
font-size: 2rem;
line-height: 1.3;
margin: 0 auto 1rem;
}
.events-grid .event .event-content p {
font-size: .875rem;
line-height: 1.5;
}
.events-grid .event .buttons .btn-cta {
background-color: red;
color: #ffffff;
font-size: 1rem;
padding: .8rem 1.5rem;
cursor: pointer;
}
@media only screen and (max-width: 700px) {
.events-grid .event {
width: 48.5%;
margin: 1% 0 1% 3%;
}
}
@media only screen and (max-width: 480px) {
.events-grid .event {
float: none;
width: 100%;
margin: 0 auto 3%;
}
}
/**************************\
Basic Modal Styles
\**************************/
.modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: 99999;
}
.modal__container {
background-color: #fff;
padding: 30px;
width: 900px;
max-width: 90%;
max-height: 100vh;
border-radius: 4px;
overflow-y: auto;
box-sizing: border-box;
}
.modal__header {
display: flex;
justify-content: space-between;
align-items: center;
}
.modal__title {
margin-top: 0;
margin-bottom: 0;
font-weight: 600;
font-size: 1.25rem;
line-height: 1.25;
color: #000000;
box-sizing: border-box;
}
.modal__close {
background: transparent;
border: 0;
}
.modal__header .modal__close:before {
content: "\2715";
}
.modal__content {
margin-top: 2rem;
margin-bottom: 2rem;
line-height: 1.5;
color: rgba(0, 0, 0, .8);
}
.modal__btn {
font-size: .875rem;
padding-left: 1rem;
padding-right: 1rem;
padding-top: .5rem;
padding-bottom: .5rem;
background-color: #e6e6e6;
color: rgba(0, 0, 0, .8);
border-radius: .25rem;
border-style: none;
border-width: 0;
cursor: pointer;
-webkit-appearance: button;
text-transform: none;
overflow: visible;
line-height: 1.15;
margin: 0;
will-change: transform;
-moz-osx-font-smoothing: grayscale;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform: translateZ(0);
transform: translateZ(0);
transition: -webkit-transform .25s ease-out;
transition: transform .25s ease-out;
transition: transform .25s ease-out, -webkit-transform .25s ease-out;
}
.modal__btn:focus,
.modal__btn:hover {
-webkit-transform: scale(1.05);
transform: scale(1.05);
}
.modal__btn-primary {
background-color: #00449e;
color: #fff;
}
/**************************\
Demo Animation Style
\**************************/
@keyframes mmfadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes mmfadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes mmslideIn {
from {
transform: translateY(15%);
}
to {
transform: translateY(0);
}
}
@keyframes mmslideOut {
from {
transform: translateY(0);
}
to {
transform: translateY(-10%);
}
}
.micromodal-slide {
display: none;
}
.micromodal-slide.is-open {
display: block;
}
.micromodal-slide[aria-hidden="false"] .modal__overlay {
animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="false"] .modal__container {
animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__overlay {
animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__container {
animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide .modal__container,
.micromodal-slide .modal__overlay {
will-change: transform;
}
.micromodal-slide .title {
font-size: 1.5625rem;
color: #111111;
border: none;
font-weight: bold;
text-transform: uppercase;
margin: 0 auto;
padding: 0;
}
.micromodal-slide .images {
margin: .5rem auto 1.5rem;
width: 100%;
height: 350px;
overflow: hidden;
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-ms-border-radius: 10px;
-o-border-radius: 10px;
}
.micromodal-slide .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.micromodal-slide h2.title {
font-size: 1.25rem;
margin: 0 auto .5rem;
}
.micromodal-slide h4.title {
font-size: 1rem;
margin: 0 auto .5rem;
}
.micromodal-slide .short_description {
font-size: 1.125rem;
font-weight: lighter;
}
.micromodal-slide .long_description {
font-size: 1rem;
margin-bottom: 1rem;
}
.micromodal-slide table.period {
border: none;
border-collapse: collapse;
width: 100%;
}
.micromodal-slide table.period td strong {
display: block;
font-size: .8125rem;
}
.micromodal-slide table.period tr {
border-top: 1px solid #dddddd;
}
.micromodal-slide table.period tr.noborder {
border: none;
}
.micromodal-slide table.period td {
border: none;
font-size: 1rem;
vertical-align: top;
padding: 1rem .5rem;
}
.micromodal-slide table.period tr.noborder td {
padding: 0 .5rem 1rem;
}
.micromodal-slide span.tag {
font-size: .8125rem;
padding: 5px 8px;
color: #ffffff;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
.micromodal-slide span.tag.canceled {
background-color: #FF0000;
}
.micromodal-slide span.tag.free-entry {
background-color: #28A745;
}
.micromodal-slide span.tag.limited {
background-color: #1668B2;
}
.micromodal-slide .tags {
margin-right: 1rem;
background-color: #f2f2f2;
font-size: .8125rem;
padding: 5px 8px;
color: #111111;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
+434
View File
@@ -0,0 +1,434 @@
.events-list {
list-style: none;
margin: 0 auto;
padding: 0;
}
.events-list .event {
border-bottom: 1px solid #5e5e5e;
margin-bottom: 1rem;
padding: 1rem 0;
display: flex;
gap: 20px;
}
.events-list .event .date {
padding: 1rem 3rem;
}
.events-list .event .date .day,
.events-list .event .date .month {
display: block;
text-align: center;
}
.events-list .event .date .day {
font-size: 2.1875rem;
font-weight: bold;
}
.events-list .event .date .month {}
.events-list .event .title {
margin: 0 auto;
padding: 0;
}
.events-list .event .description {
margin: .5rem auto;
padding: 0;
line-height: 1.3;
}
.events-list .event .images {
width: 200px;
height: 200px;
}
.events-list .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-list .event .event-info {
display: flex;
gap: 20px;
}
.events-grid .event {
float: left;
width: 31.33%;
margin: 1% 0 1% 3%;
border: none !important;
display: block;
}
.events-grid .event:after {
clear: both;
}
.events-grid .event:first-child {
margin-left: 0;
}
.events-grid .event .images {
display: block;
width: 100%;
height: 300px;
margin-bottom: 1rem;
position: relative;
}
.events-grid .event .images .date {
position: absolute;
left: 0;
bottom: 0;
color: #ffffff;
background-color: red;
padding: .8rem 1.5rem;
}
.events-grid .event .date .day {
font-size: 1.875rem;
font-weight: bold;
display: block;
text-align: center;
}
.events-grid .event .date .month {
display: block;
text-align: center;
}
.events-grid .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-grid .event .event-content .title {
font-size: 2rem;
line-height: 1.3;
margin: 0 auto 1rem;
}
.events-grid .event .event-content p {
font-size: .875rem;
line-height: 1.5;
}
.events-grid .event .buttons .btn-cta {
background-color: red;
color: #ffffff;
font-size: 1rem;
padding: .8rem 1.5rem;
cursor: pointer;
}
@media only screen and (max-width: 700px) {
.events-grid .event {
width: 48.5%;
margin: 1% 0 1% 3%;
}
}
@media only screen and (max-width: 480px) {
.events-grid .event {
float: none;
width: 100%;
margin: 0 auto 3%;
}
}
/**************************\
Basic Modal Styles
\**************************/
.modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: 99999;
}
.modal__container {
background-color: #fff;
padding: 30px;
width: 900px;
max-width: 90%;
max-height: 100vh;
border-radius: 4px;
overflow-y: auto;
box-sizing: border-box;
}
.modal__header {
display: flex;
justify-content: space-between;
align-items: center;
}
.modal__title {
margin-top: 0;
margin-bottom: 0;
font-weight: 600;
font-size: 1.25rem;
line-height: 1.25;
color: #000000;
box-sizing: border-box;
}
.modal__close {
background: transparent;
border: 0;
}
.modal__header .modal__close:before {
content: "\2715";
}
.modal__content {
margin-top: 2rem;
margin-bottom: 2rem;
line-height: 1.5;
color: rgba(0, 0, 0, .8);
}
.modal__btn {
font-size: .875rem;
padding-left: 1rem;
padding-right: 1rem;
padding-top: .5rem;
padding-bottom: .5rem;
background-color: #e6e6e6;
color: rgba(0, 0, 0, .8);
border-radius: .25rem;
border-style: none;
border-width: 0;
cursor: pointer;
-webkit-appearance: button;
text-transform: none;
overflow: visible;
line-height: 1.15;
margin: 0;
will-change: transform;
-moz-osx-font-smoothing: grayscale;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform: translateZ(0);
transform: translateZ(0);
transition: -webkit-transform .25s ease-out;
transition: transform .25s ease-out;
transition: transform .25s ease-out, -webkit-transform .25s ease-out;
}
.modal__btn:focus,
.modal__btn:hover {
-webkit-transform: scale(1.05);
transform: scale(1.05);
}
.modal__btn-primary {
background-color: #00449e;
color: #fff;
}
/**************************\
Demo Animation Style
\**************************/
@keyframes mmfadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes mmfadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes mmslideIn {
from {
transform: translateY(15%);
}
to {
transform: translateY(0);
}
}
@keyframes mmslideOut {
from {
transform: translateY(0);
}
to {
transform: translateY(-10%);
}
}
.micromodal-slide {
display: none;
}
.micromodal-slide.is-open {
display: block;
}
.micromodal-slide[aria-hidden="false"] .modal__overlay {
animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="false"] .modal__container {
animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__overlay {
animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__container {
animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide .modal__container,
.micromodal-slide .modal__overlay {
will-change: transform;
}
.micromodal-slide .title {
font-size: 1.5625rem;
color: #111111;
border: none;
font-weight: bold;
text-transform: uppercase;
margin: 0 auto;
padding: 0;
}
.micromodal-slide .images {
margin: .5rem auto 1.5rem;
width: 100%;
height: 350px;
overflow: hidden;
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-ms-border-radius: 10px;
-o-border-radius: 10px;
}
.micromodal-slide .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.micromodal-slide h2.title {
font-size: 1.25rem;
margin: 0 auto .5rem;
}
.micromodal-slide h4.title {
font-size: 1rem;
margin: 0 auto .5rem;
}
.micromodal-slide .short_description {
font-size: 1.125rem;
font-weight: lighter;
}
.micromodal-slide .long_description {
font-size: 1rem;
margin-bottom: 1rem;
}
.micromodal-slide table.period {
border: none;
border-collapse: collapse;
width: 100%;
}
.micromodal-slide table.period td strong {
display: block;
font-size: .8125rem;
}
.micromodal-slide table.period tr {
border-top: 1px solid #dddddd;
}
.micromodal-slide table.period tr.noborder {
border: none;
}
.micromodal-slide table.period td {
border: none;
font-size: 1rem;
vertical-align: top;
padding: 1rem .5rem;
}
.micromodal-slide table.period tr.noborder td {
padding: 0 .5rem 1rem;
}
.micromodal-slide span.tag {
font-size: .8125rem;
padding: 5px 8px;
color: #ffffff;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
.micromodal-slide span.tag.canceled {
background-color: #FF0000;
}
.micromodal-slide span.tag.free-entry {
background-color: #28A745;
}
.micromodal-slide span.tag.limited {
background-color: #1668B2;
}
.micromodal-slide .tags {
margin-right: 1rem;
background-color: #f2f2f2;
font-size: .8125rem;
padding: 5px 8px;
color: #111111;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
+1
View File
@@ -0,0 +1 @@
{"version":"1.0","provider_name":"Rijecki sportski savez","provider_url":"https:\/\/rss.hr","author_name":"Slavisa Bradic","author_url":"https:\/\/rss.hr\/author\/slavisa-bradic\/","title":"DATA Digital Administrative and Training Assistant","type":"rich","width":600,"height":338,"html":"<blockquote class=\"wp-embedded-content\" data-secret=\"60d66L8n3j\"><a href=\"https:\/\/rss.hr\/data-digital-administrative-and-training-assistant\/\">DATA Digital Administrative and Training Assistant<\/a><\/blockquote><iframe sandbox=\"allow-scripts\" security=\"restricted\" src=\"https:\/\/rss.hr\/data-digital-administrative-and-training-assistant\/embed\/#?secret=60d66L8n3j\" width=\"600\" height=\"338\" title=\"&#8220;DATA Digital Administrative and Training Assistant&#8221; &#8212; Rijecki sportski savez\" data-secret=\"60d66L8n3j\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\" class=\"wp-embedded-content\"><\/iframe><script>\n\/*! This file is auto-generated *\/\n!function(d,l){\"use strict\";l.querySelector&&d.addEventListener&&\"undefined\"!=typeof URL&&(d.wp=d.wp||{},d.wp.receiveEmbedMessage||(d.wp.receiveEmbedMessage=function(e){var t=e.data;if((t||t.secret||t.message||t.value)&&!\/[^a-zA-Z0-9]\/.test(t.secret)){for(var s,r,n,a=l.querySelectorAll('iframe[data-secret=\"'+t.secret+'\"]'),o=l.querySelectorAll('blockquote[data-secret=\"'+t.secret+'\"]'),c=new RegExp(\"^https?:$\",\"i\"),i=0;i<o.length;i++)o[i].style.display=\"none\";for(i=0;i<a.length;i++)s=a[i],e.source===s.contentWindow&&(s.removeAttribute(\"style\"),\"height\"===t.message?(1e3<(r=parseInt(t.value,10))?r=1e3:~~r<200&&(r=200),s.height=r):\"link\"===t.message&&(r=new URL(s.getAttribute(\"src\")),n=new URL(t.value),c.test(n.protocol))&&n.host===r.host&&l.activeElement===s&&(d.top.location.href=t.value))}},d.addEventListener(\"message\",d.wp.receiveEmbedMessage,!1),l.addEventListener(\"DOMContentLoaded\",function(){for(var e,t,s=l.querySelectorAll(\"iframe.wp-embedded-content\"),r=0;r<s.length;r++)(t=(e=s[r]).getAttribute(\"data-secret\"))||(t=Math.random().toString(36).substring(2,12),e.src+=\"#?secret=\"+t,e.setAttribute(\"data-secret\",t)),e.contentWindow.postMessage({message:\"ready\",secret:t},\"*\")},!1)))}(window,document);\n\/\/# sourceURL=https:\/\/rss.hr\/wp-includes\/js\/wp-embed.min.js\n<\/script>\n"}
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
{"version":"1.0","provider_name":"Rijecki sportski savez","provider_url":"https:\/\/rss.hr","author_name":"RSS","author_url":"https:\/\/rss.hr\/author\/sasa\/","title":"Javni poziv za prikupljanje prijedloga programa za izradu programa javnih potreba u sportu Grada Rijeke u 2026. Godini.","type":"rich","width":600,"height":338,"html":"<blockquote class=\"wp-embedded-content\" data-secret=\"a5cuIZo0TH\"><a href=\"https:\/\/rss.hr\/javni-poziv-za-prikupljanje-prijedloga-programa-za-izradu-programa-javnih-potreba-u-sportu-grada-rijeke-u-2026-godini\/\">Javni poziv za prikupljanje prijedloga programa za izradu programa javnih potreba u sportu Grada Rijeke u 2026. Godini.<\/a><\/blockquote><iframe sandbox=\"allow-scripts\" security=\"restricted\" src=\"https:\/\/rss.hr\/javni-poziv-za-prikupljanje-prijedloga-programa-za-izradu-programa-javnih-potreba-u-sportu-grada-rijeke-u-2026-godini\/embed\/#?secret=a5cuIZo0TH\" width=\"600\" height=\"338\" title=\"&#8220;Javni poziv za prikupljanje prijedloga programa za izradu programa javnih potreba u sportu Grada Rijeke u 2026. Godini.&#8221; &#8212; Rijecki sportski savez\" data-secret=\"a5cuIZo0TH\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\" class=\"wp-embedded-content\"><\/iframe><script>\n\/*! This file is auto-generated *\/\n!function(d,l){\"use strict\";l.querySelector&&d.addEventListener&&\"undefined\"!=typeof URL&&(d.wp=d.wp||{},d.wp.receiveEmbedMessage||(d.wp.receiveEmbedMessage=function(e){var t=e.data;if((t||t.secret||t.message||t.value)&&!\/[^a-zA-Z0-9]\/.test(t.secret)){for(var s,r,n,a=l.querySelectorAll('iframe[data-secret=\"'+t.secret+'\"]'),o=l.querySelectorAll('blockquote[data-secret=\"'+t.secret+'\"]'),c=new RegExp(\"^https?:$\",\"i\"),i=0;i<o.length;i++)o[i].style.display=\"none\";for(i=0;i<a.length;i++)s=a[i],e.source===s.contentWindow&&(s.removeAttribute(\"style\"),\"height\"===t.message?(1e3<(r=parseInt(t.value,10))?r=1e3:~~r<200&&(r=200),s.height=r):\"link\"===t.message&&(r=new URL(s.getAttribute(\"src\")),n=new URL(t.value),c.test(n.protocol))&&n.host===r.host&&l.activeElement===s&&(d.top.location.href=t.value))}},d.addEventListener(\"message\",d.wp.receiveEmbedMessage,!1),l.addEventListener(\"DOMContentLoaded\",function(){for(var e,t,s=l.querySelectorAll(\"iframe.wp-embedded-content\"),r=0;r<s.length;r++)(t=(e=s[r]).getAttribute(\"data-secret\"))||(t=Math.random().toString(36).substring(2,12),e.src+=\"#?secret=\"+t,e.setAttribute(\"data-secret\",t)),e.contentWindow.postMessage({message:\"ready\",secret:t},\"*\")},!1)))}(window,document);\n\/\/# sourceURL=https:\/\/rss.hr\/wp-includes\/js\/wp-embed.min.js\n<\/script>\n","thumbnail_url":"https:\/\/rss.hr\/wp-content\/uploads\/2025\/09\/rss-logo-768x420.png","thumbnail_width":600,"thumbnail_height":328}
+1
View File
@@ -0,0 +1 @@
.elementor-3853 .elementor-element.elementor-element-5a7d978d:not(.elementor-motion-effects-element-type-background), .elementor-3853 .elementor-element.elementor-element-5a7d978d > .elementor-motion-effects-container > .elementor-motion-effects-layer{background-color:#494949;background-image:url("https://rss.hr/wp-content/uploads/2021/08/justice-law-case-1509437.jpg");background-position:bottom center;background-size:cover;}.elementor-3853 .elementor-element.elementor-element-5a7d978d > .elementor-background-overlay{background-color:transparent;background-image:linear-gradient(180deg, #153243 0%, #1A6C7A 100%);opacity:0.6;transition:background 0.3s, border-radius 0.3s, opacity 0.3s;}.elementor-3853 .elementor-element.elementor-element-5a7d978d > .elementor-container{min-height:300px;}.elementor-3853 .elementor-element.elementor-element-5a7d978d{transition:background 0.3s, border 0.3s, border-radius 0.3s, box-shadow 0.3s;padding:200px 0px 100px 0px;}.elementor-3853 .elementor-element.elementor-element-36e3182{text-align:center;}.elementor-3853 .elementor-element.elementor-element-36e3182 .elementor-heading-title{letter-spacing:2.5px;color:#FFFFFF;}.elementor-3853 .elementor-element.elementor-element-14aab00{--spacer-size:10px;}.elementor-3853 .elementor-element.elementor-element-3db69b3 .elementor-toggle-title{font-weight:bold;}.elementor-3853 .elementor-element.elementor-element-3db69b3 .elementor-tab-content{text-transform:none;}.elementor-3853 .elementor-element.elementor-element-f9184cf{text-align:start;}body.elementor-page-3853:not(.elementor-motion-effects-element-type-background), body.elementor-page-3853 > .elementor-motion-effects-container > .elementor-motion-effects-layer{background-color:#ffffff;}@media(min-width:1025px){.elementor-3853 .elementor-element.elementor-element-5a7d978d:not(.elementor-motion-effects-element-type-background), .elementor-3853 .elementor-element.elementor-element-5a7d978d > .elementor-motion-effects-container > .elementor-motion-effects-layer{background-attachment:fixed;}}@media(max-width:1024px){.elementor-3853 .elementor-element.elementor-element-5a7d978d{padding:140px 80px 80px 80px;}.elementor-3853 .elementor-element.elementor-element-36e3182 .elementor-heading-title{font-size:40px;}}@media(max-width:767px){.elementor-3853 .elementor-element.elementor-element-5a7d978d{padding:0px 0px 0px 0px;}.elementor-3853 .elementor-element.elementor-element-7b7c0473{width:100%;}.elementor-3853 .elementor-element.elementor-element-36e3182 .elementor-heading-title{font-size:30px;}}
+434
View File
@@ -0,0 +1,434 @@
.events-list {
list-style: none;
margin: 0 auto;
padding: 0;
}
.events-list .event {
border-bottom: 1px solid #5e5e5e;
margin-bottom: 1rem;
padding: 1rem 0;
display: flex;
gap: 20px;
}
.events-list .event .date {
padding: 1rem 3rem;
}
.events-list .event .date .day,
.events-list .event .date .month {
display: block;
text-align: center;
}
.events-list .event .date .day {
font-size: 2.1875rem;
font-weight: bold;
}
.events-list .event .date .month {}
.events-list .event .title {
margin: 0 auto;
padding: 0;
}
.events-list .event .description {
margin: .5rem auto;
padding: 0;
line-height: 1.3;
}
.events-list .event .images {
width: 200px;
height: 200px;
}
.events-list .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-list .event .event-info {
display: flex;
gap: 20px;
}
.events-grid .event {
float: left;
width: 31.33%;
margin: 1% 0 1% 3%;
border: none !important;
display: block;
}
.events-grid .event:after {
clear: both;
}
.events-grid .event:first-child {
margin-left: 0;
}
.events-grid .event .images {
display: block;
width: 100%;
height: 300px;
margin-bottom: 1rem;
position: relative;
}
.events-grid .event .images .date {
position: absolute;
left: 0;
bottom: 0;
color: #ffffff;
background-color: red;
padding: .8rem 1.5rem;
}
.events-grid .event .date .day {
font-size: 1.875rem;
font-weight: bold;
display: block;
text-align: center;
}
.events-grid .event .date .month {
display: block;
text-align: center;
}
.events-grid .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-grid .event .event-content .title {
font-size: 2rem;
line-height: 1.3;
margin: 0 auto 1rem;
}
.events-grid .event .event-content p {
font-size: .875rem;
line-height: 1.5;
}
.events-grid .event .buttons .btn-cta {
background-color: red;
color: #ffffff;
font-size: 1rem;
padding: .8rem 1.5rem;
cursor: pointer;
}
@media only screen and (max-width: 700px) {
.events-grid .event {
width: 48.5%;
margin: 1% 0 1% 3%;
}
}
@media only screen and (max-width: 480px) {
.events-grid .event {
float: none;
width: 100%;
margin: 0 auto 3%;
}
}
/**************************\
Basic Modal Styles
\**************************/
.modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: 99999;
}
.modal__container {
background-color: #fff;
padding: 30px;
width: 900px;
max-width: 90%;
max-height: 100vh;
border-radius: 4px;
overflow-y: auto;
box-sizing: border-box;
}
.modal__header {
display: flex;
justify-content: space-between;
align-items: center;
}
.modal__title {
margin-top: 0;
margin-bottom: 0;
font-weight: 600;
font-size: 1.25rem;
line-height: 1.25;
color: #000000;
box-sizing: border-box;
}
.modal__close {
background: transparent;
border: 0;
}
.modal__header .modal__close:before {
content: "\2715";
}
.modal__content {
margin-top: 2rem;
margin-bottom: 2rem;
line-height: 1.5;
color: rgba(0, 0, 0, .8);
}
.modal__btn {
font-size: .875rem;
padding-left: 1rem;
padding-right: 1rem;
padding-top: .5rem;
padding-bottom: .5rem;
background-color: #e6e6e6;
color: rgba(0, 0, 0, .8);
border-radius: .25rem;
border-style: none;
border-width: 0;
cursor: pointer;
-webkit-appearance: button;
text-transform: none;
overflow: visible;
line-height: 1.15;
margin: 0;
will-change: transform;
-moz-osx-font-smoothing: grayscale;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform: translateZ(0);
transform: translateZ(0);
transition: -webkit-transform .25s ease-out;
transition: transform .25s ease-out;
transition: transform .25s ease-out, -webkit-transform .25s ease-out;
}
.modal__btn:focus,
.modal__btn:hover {
-webkit-transform: scale(1.05);
transform: scale(1.05);
}
.modal__btn-primary {
background-color: #00449e;
color: #fff;
}
/**************************\
Demo Animation Style
\**************************/
@keyframes mmfadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes mmfadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes mmslideIn {
from {
transform: translateY(15%);
}
to {
transform: translateY(0);
}
}
@keyframes mmslideOut {
from {
transform: translateY(0);
}
to {
transform: translateY(-10%);
}
}
.micromodal-slide {
display: none;
}
.micromodal-slide.is-open {
display: block;
}
.micromodal-slide[aria-hidden="false"] .modal__overlay {
animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="false"] .modal__container {
animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__overlay {
animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__container {
animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide .modal__container,
.micromodal-slide .modal__overlay {
will-change: transform;
}
.micromodal-slide .title {
font-size: 1.5625rem;
color: #111111;
border: none;
font-weight: bold;
text-transform: uppercase;
margin: 0 auto;
padding: 0;
}
.micromodal-slide .images {
margin: .5rem auto 1.5rem;
width: 100%;
height: 350px;
overflow: hidden;
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-ms-border-radius: 10px;
-o-border-radius: 10px;
}
.micromodal-slide .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.micromodal-slide h2.title {
font-size: 1.25rem;
margin: 0 auto .5rem;
}
.micromodal-slide h4.title {
font-size: 1rem;
margin: 0 auto .5rem;
}
.micromodal-slide .short_description {
font-size: 1.125rem;
font-weight: lighter;
}
.micromodal-slide .long_description {
font-size: 1rem;
margin-bottom: 1rem;
}
.micromodal-slide table.period {
border: none;
border-collapse: collapse;
width: 100%;
}
.micromodal-slide table.period td strong {
display: block;
font-size: .8125rem;
}
.micromodal-slide table.period tr {
border-top: 1px solid #dddddd;
}
.micromodal-slide table.period tr.noborder {
border: none;
}
.micromodal-slide table.period td {
border: none;
font-size: 1rem;
vertical-align: top;
padding: 1rem .5rem;
}
.micromodal-slide table.period tr.noborder td {
padding: 0 .5rem 1rem;
}
.micromodal-slide span.tag {
font-size: .8125rem;
padding: 5px 8px;
color: #ffffff;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
.micromodal-slide span.tag.canceled {
background-color: #FF0000;
}
.micromodal-slide span.tag.free-entry {
background-color: #28A745;
}
.micromodal-slide span.tag.limited {
background-color: #1668B2;
}
.micromodal-slide .tags {
margin-right: 1rem;
background-color: #f2f2f2;
font-size: .8125rem;
padding: 5px 8px;
color: #111111;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
+434
View File
@@ -0,0 +1,434 @@
.events-list {
list-style: none;
margin: 0 auto;
padding: 0;
}
.events-list .event {
border-bottom: 1px solid #5e5e5e;
margin-bottom: 1rem;
padding: 1rem 0;
display: flex;
gap: 20px;
}
.events-list .event .date {
padding: 1rem 3rem;
}
.events-list .event .date .day,
.events-list .event .date .month {
display: block;
text-align: center;
}
.events-list .event .date .day {
font-size: 2.1875rem;
font-weight: bold;
}
.events-list .event .date .month {}
.events-list .event .title {
margin: 0 auto;
padding: 0;
}
.events-list .event .description {
margin: .5rem auto;
padding: 0;
line-height: 1.3;
}
.events-list .event .images {
width: 200px;
height: 200px;
}
.events-list .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-list .event .event-info {
display: flex;
gap: 20px;
}
.events-grid .event {
float: left;
width: 31.33%;
margin: 1% 0 1% 3%;
border: none !important;
display: block;
}
.events-grid .event:after {
clear: both;
}
.events-grid .event:first-child {
margin-left: 0;
}
.events-grid .event .images {
display: block;
width: 100%;
height: 300px;
margin-bottom: 1rem;
position: relative;
}
.events-grid .event .images .date {
position: absolute;
left: 0;
bottom: 0;
color: #ffffff;
background-color: red;
padding: .8rem 1.5rem;
}
.events-grid .event .date .day {
font-size: 1.875rem;
font-weight: bold;
display: block;
text-align: center;
}
.events-grid .event .date .month {
display: block;
text-align: center;
}
.events-grid .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-grid .event .event-content .title {
font-size: 2rem;
line-height: 1.3;
margin: 0 auto 1rem;
}
.events-grid .event .event-content p {
font-size: .875rem;
line-height: 1.5;
}
.events-grid .event .buttons .btn-cta {
background-color: red;
color: #ffffff;
font-size: 1rem;
padding: .8rem 1.5rem;
cursor: pointer;
}
@media only screen and (max-width: 700px) {
.events-grid .event {
width: 48.5%;
margin: 1% 0 1% 3%;
}
}
@media only screen and (max-width: 480px) {
.events-grid .event {
float: none;
width: 100%;
margin: 0 auto 3%;
}
}
/**************************\
Basic Modal Styles
\**************************/
.modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: 99999;
}
.modal__container {
background-color: #fff;
padding: 30px;
width: 900px;
max-width: 90%;
max-height: 100vh;
border-radius: 4px;
overflow-y: auto;
box-sizing: border-box;
}
.modal__header {
display: flex;
justify-content: space-between;
align-items: center;
}
.modal__title {
margin-top: 0;
margin-bottom: 0;
font-weight: 600;
font-size: 1.25rem;
line-height: 1.25;
color: #000000;
box-sizing: border-box;
}
.modal__close {
background: transparent;
border: 0;
}
.modal__header .modal__close:before {
content: "\2715";
}
.modal__content {
margin-top: 2rem;
margin-bottom: 2rem;
line-height: 1.5;
color: rgba(0, 0, 0, .8);
}
.modal__btn {
font-size: .875rem;
padding-left: 1rem;
padding-right: 1rem;
padding-top: .5rem;
padding-bottom: .5rem;
background-color: #e6e6e6;
color: rgba(0, 0, 0, .8);
border-radius: .25rem;
border-style: none;
border-width: 0;
cursor: pointer;
-webkit-appearance: button;
text-transform: none;
overflow: visible;
line-height: 1.15;
margin: 0;
will-change: transform;
-moz-osx-font-smoothing: grayscale;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform: translateZ(0);
transform: translateZ(0);
transition: -webkit-transform .25s ease-out;
transition: transform .25s ease-out;
transition: transform .25s ease-out, -webkit-transform .25s ease-out;
}
.modal__btn:focus,
.modal__btn:hover {
-webkit-transform: scale(1.05);
transform: scale(1.05);
}
.modal__btn-primary {
background-color: #00449e;
color: #fff;
}
/**************************\
Demo Animation Style
\**************************/
@keyframes mmfadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes mmfadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes mmslideIn {
from {
transform: translateY(15%);
}
to {
transform: translateY(0);
}
}
@keyframes mmslideOut {
from {
transform: translateY(0);
}
to {
transform: translateY(-10%);
}
}
.micromodal-slide {
display: none;
}
.micromodal-slide.is-open {
display: block;
}
.micromodal-slide[aria-hidden="false"] .modal__overlay {
animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="false"] .modal__container {
animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__overlay {
animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__container {
animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide .modal__container,
.micromodal-slide .modal__overlay {
will-change: transform;
}
.micromodal-slide .title {
font-size: 1.5625rem;
color: #111111;
border: none;
font-weight: bold;
text-transform: uppercase;
margin: 0 auto;
padding: 0;
}
.micromodal-slide .images {
margin: .5rem auto 1.5rem;
width: 100%;
height: 350px;
overflow: hidden;
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-ms-border-radius: 10px;
-o-border-radius: 10px;
}
.micromodal-slide .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.micromodal-slide h2.title {
font-size: 1.25rem;
margin: 0 auto .5rem;
}
.micromodal-slide h4.title {
font-size: 1rem;
margin: 0 auto .5rem;
}
.micromodal-slide .short_description {
font-size: 1.125rem;
font-weight: lighter;
}
.micromodal-slide .long_description {
font-size: 1rem;
margin-bottom: 1rem;
}
.micromodal-slide table.period {
border: none;
border-collapse: collapse;
width: 100%;
}
.micromodal-slide table.period td strong {
display: block;
font-size: .8125rem;
}
.micromodal-slide table.period tr {
border-top: 1px solid #dddddd;
}
.micromodal-slide table.period tr.noborder {
border: none;
}
.micromodal-slide table.period td {
border: none;
font-size: 1rem;
vertical-align: top;
padding: 1rem .5rem;
}
.micromodal-slide table.period tr.noborder td {
padding: 0 .5rem 1rem;
}
.micromodal-slide span.tag {
font-size: .8125rem;
padding: 5px 8px;
color: #ffffff;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
.micromodal-slide span.tag.canceled {
background-color: #FF0000;
}
.micromodal-slide span.tag.free-entry {
background-color: #28A745;
}
.micromodal-slide span.tag.limited {
background-color: #1668B2;
}
.micromodal-slide .tags {
margin-right: 1rem;
background-color: #f2f2f2;
font-size: .8125rem;
padding: 5px 8px;
color: #111111;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+434
View File
@@ -0,0 +1,434 @@
.events-list {
list-style: none;
margin: 0 auto;
padding: 0;
}
.events-list .event {
border-bottom: 1px solid #5e5e5e;
margin-bottom: 1rem;
padding: 1rem 0;
display: flex;
gap: 20px;
}
.events-list .event .date {
padding: 1rem 3rem;
}
.events-list .event .date .day,
.events-list .event .date .month {
display: block;
text-align: center;
}
.events-list .event .date .day {
font-size: 2.1875rem;
font-weight: bold;
}
.events-list .event .date .month {}
.events-list .event .title {
margin: 0 auto;
padding: 0;
}
.events-list .event .description {
margin: .5rem auto;
padding: 0;
line-height: 1.3;
}
.events-list .event .images {
width: 200px;
height: 200px;
}
.events-list .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-list .event .event-info {
display: flex;
gap: 20px;
}
.events-grid .event {
float: left;
width: 31.33%;
margin: 1% 0 1% 3%;
border: none !important;
display: block;
}
.events-grid .event:after {
clear: both;
}
.events-grid .event:first-child {
margin-left: 0;
}
.events-grid .event .images {
display: block;
width: 100%;
height: 300px;
margin-bottom: 1rem;
position: relative;
}
.events-grid .event .images .date {
position: absolute;
left: 0;
bottom: 0;
color: #ffffff;
background-color: red;
padding: .8rem 1.5rem;
}
.events-grid .event .date .day {
font-size: 1.875rem;
font-weight: bold;
display: block;
text-align: center;
}
.events-grid .event .date .month {
display: block;
text-align: center;
}
.events-grid .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-grid .event .event-content .title {
font-size: 2rem;
line-height: 1.3;
margin: 0 auto 1rem;
}
.events-grid .event .event-content p {
font-size: .875rem;
line-height: 1.5;
}
.events-grid .event .buttons .btn-cta {
background-color: red;
color: #ffffff;
font-size: 1rem;
padding: .8rem 1.5rem;
cursor: pointer;
}
@media only screen and (max-width: 700px) {
.events-grid .event {
width: 48.5%;
margin: 1% 0 1% 3%;
}
}
@media only screen and (max-width: 480px) {
.events-grid .event {
float: none;
width: 100%;
margin: 0 auto 3%;
}
}
/**************************\
Basic Modal Styles
\**************************/
.modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: 99999;
}
.modal__container {
background-color: #fff;
padding: 30px;
width: 900px;
max-width: 90%;
max-height: 100vh;
border-radius: 4px;
overflow-y: auto;
box-sizing: border-box;
}
.modal__header {
display: flex;
justify-content: space-between;
align-items: center;
}
.modal__title {
margin-top: 0;
margin-bottom: 0;
font-weight: 600;
font-size: 1.25rem;
line-height: 1.25;
color: #000000;
box-sizing: border-box;
}
.modal__close {
background: transparent;
border: 0;
}
.modal__header .modal__close:before {
content: "\2715";
}
.modal__content {
margin-top: 2rem;
margin-bottom: 2rem;
line-height: 1.5;
color: rgba(0, 0, 0, .8);
}
.modal__btn {
font-size: .875rem;
padding-left: 1rem;
padding-right: 1rem;
padding-top: .5rem;
padding-bottom: .5rem;
background-color: #e6e6e6;
color: rgba(0, 0, 0, .8);
border-radius: .25rem;
border-style: none;
border-width: 0;
cursor: pointer;
-webkit-appearance: button;
text-transform: none;
overflow: visible;
line-height: 1.15;
margin: 0;
will-change: transform;
-moz-osx-font-smoothing: grayscale;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform: translateZ(0);
transform: translateZ(0);
transition: -webkit-transform .25s ease-out;
transition: transform .25s ease-out;
transition: transform .25s ease-out, -webkit-transform .25s ease-out;
}
.modal__btn:focus,
.modal__btn:hover {
-webkit-transform: scale(1.05);
transform: scale(1.05);
}
.modal__btn-primary {
background-color: #00449e;
color: #fff;
}
/**************************\
Demo Animation Style
\**************************/
@keyframes mmfadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes mmfadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes mmslideIn {
from {
transform: translateY(15%);
}
to {
transform: translateY(0);
}
}
@keyframes mmslideOut {
from {
transform: translateY(0);
}
to {
transform: translateY(-10%);
}
}
.micromodal-slide {
display: none;
}
.micromodal-slide.is-open {
display: block;
}
.micromodal-slide[aria-hidden="false"] .modal__overlay {
animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="false"] .modal__container {
animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__overlay {
animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__container {
animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide .modal__container,
.micromodal-slide .modal__overlay {
will-change: transform;
}
.micromodal-slide .title {
font-size: 1.5625rem;
color: #111111;
border: none;
font-weight: bold;
text-transform: uppercase;
margin: 0 auto;
padding: 0;
}
.micromodal-slide .images {
margin: .5rem auto 1.5rem;
width: 100%;
height: 350px;
overflow: hidden;
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-ms-border-radius: 10px;
-o-border-radius: 10px;
}
.micromodal-slide .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.micromodal-slide h2.title {
font-size: 1.25rem;
margin: 0 auto .5rem;
}
.micromodal-slide h4.title {
font-size: 1rem;
margin: 0 auto .5rem;
}
.micromodal-slide .short_description {
font-size: 1.125rem;
font-weight: lighter;
}
.micromodal-slide .long_description {
font-size: 1rem;
margin-bottom: 1rem;
}
.micromodal-slide table.period {
border: none;
border-collapse: collapse;
width: 100%;
}
.micromodal-slide table.period td strong {
display: block;
font-size: .8125rem;
}
.micromodal-slide table.period tr {
border-top: 1px solid #dddddd;
}
.micromodal-slide table.period tr.noborder {
border: none;
}
.micromodal-slide table.period td {
border: none;
font-size: 1rem;
vertical-align: top;
padding: 1rem .5rem;
}
.micromodal-slide table.period tr.noborder td {
padding: 0 .5rem 1rem;
}
.micromodal-slide span.tag {
font-size: .8125rem;
padding: 5px 8px;
color: #ffffff;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
.micromodal-slide span.tag.canceled {
background-color: #FF0000;
}
.micromodal-slide span.tag.free-entry {
background-color: #28A745;
}
.micromodal-slide span.tag.limited {
background-color: #1668B2;
}
.micromodal-slide .tags {
margin-right: 1rem;
background-color: #f2f2f2;
font-size: .8125rem;
padding: 5px 8px;
color: #111111;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
{"version":"1.0","provider_name":"Rijecki sportski savez","provider_url":"https:\/\/rss.hr","author_name":"RSS","author_url":"https:\/\/rss.hr\/author\/sasa\/","title":"Objekti Rijeka Sporta sa svojim sadr\u017eajem","type":"rich","width":600,"height":338,"html":"<blockquote class=\"wp-embedded-content\" data-secret=\"0gzRDd23TE\"><a href=\"https:\/\/rss.hr\/objekti-rijeka-sporta-sa-svojim-sadrzajem\/\">Objekti Rijeka Sporta sa svojim sadr\u017eajem<\/a><\/blockquote><iframe sandbox=\"allow-scripts\" security=\"restricted\" src=\"https:\/\/rss.hr\/objekti-rijeka-sporta-sa-svojim-sadrzajem\/embed\/#?secret=0gzRDd23TE\" width=\"600\" height=\"338\" title=\"&#8220;Objekti Rijeka Sporta sa svojim sadr\u017eajem&#8221; &#8212; Rijecki sportski savez\" data-secret=\"0gzRDd23TE\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\" class=\"wp-embedded-content\"><\/iframe><script>\n\/*! This file is auto-generated *\/\n!function(d,l){\"use strict\";l.querySelector&&d.addEventListener&&\"undefined\"!=typeof URL&&(d.wp=d.wp||{},d.wp.receiveEmbedMessage||(d.wp.receiveEmbedMessage=function(e){var t=e.data;if((t||t.secret||t.message||t.value)&&!\/[^a-zA-Z0-9]\/.test(t.secret)){for(var s,r,n,a=l.querySelectorAll('iframe[data-secret=\"'+t.secret+'\"]'),o=l.querySelectorAll('blockquote[data-secret=\"'+t.secret+'\"]'),c=new RegExp(\"^https?:$\",\"i\"),i=0;i<o.length;i++)o[i].style.display=\"none\";for(i=0;i<a.length;i++)s=a[i],e.source===s.contentWindow&&(s.removeAttribute(\"style\"),\"height\"===t.message?(1e3<(r=parseInt(t.value,10))?r=1e3:~~r<200&&(r=200),s.height=r):\"link\"===t.message&&(r=new URL(s.getAttribute(\"src\")),n=new URL(t.value),c.test(n.protocol))&&n.host===r.host&&l.activeElement===s&&(d.top.location.href=t.value))}},d.addEventListener(\"message\",d.wp.receiveEmbedMessage,!1),l.addEventListener(\"DOMContentLoaded\",function(){for(var e,t,s=l.querySelectorAll(\"iframe.wp-embedded-content\"),r=0;r<s.length;r++)(t=(e=s[r]).getAttribute(\"data-secret\"))||(t=Math.random().toString(36).substring(2,12),e.src+=\"#?secret=\"+t,e.setAttribute(\"data-secret\",t)),e.contentWindow.postMessage({message:\"ready\",secret:t},\"*\")},!1)))}(window,document);\n\/\/# sourceURL=https:\/\/rss.hr\/wp-includes\/js\/wp-embed.min.js\n<\/script>\n"}
Binary file not shown.
+434
View File
@@ -0,0 +1,434 @@
.events-list {
list-style: none;
margin: 0 auto;
padding: 0;
}
.events-list .event {
border-bottom: 1px solid #5e5e5e;
margin-bottom: 1rem;
padding: 1rem 0;
display: flex;
gap: 20px;
}
.events-list .event .date {
padding: 1rem 3rem;
}
.events-list .event .date .day,
.events-list .event .date .month {
display: block;
text-align: center;
}
.events-list .event .date .day {
font-size: 2.1875rem;
font-weight: bold;
}
.events-list .event .date .month {}
.events-list .event .title {
margin: 0 auto;
padding: 0;
}
.events-list .event .description {
margin: .5rem auto;
padding: 0;
line-height: 1.3;
}
.events-list .event .images {
width: 200px;
height: 200px;
}
.events-list .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-list .event .event-info {
display: flex;
gap: 20px;
}
.events-grid .event {
float: left;
width: 31.33%;
margin: 1% 0 1% 3%;
border: none !important;
display: block;
}
.events-grid .event:after {
clear: both;
}
.events-grid .event:first-child {
margin-left: 0;
}
.events-grid .event .images {
display: block;
width: 100%;
height: 300px;
margin-bottom: 1rem;
position: relative;
}
.events-grid .event .images .date {
position: absolute;
left: 0;
bottom: 0;
color: #ffffff;
background-color: red;
padding: .8rem 1.5rem;
}
.events-grid .event .date .day {
font-size: 1.875rem;
font-weight: bold;
display: block;
text-align: center;
}
.events-grid .event .date .month {
display: block;
text-align: center;
}
.events-grid .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-grid .event .event-content .title {
font-size: 2rem;
line-height: 1.3;
margin: 0 auto 1rem;
}
.events-grid .event .event-content p {
font-size: .875rem;
line-height: 1.5;
}
.events-grid .event .buttons .btn-cta {
background-color: red;
color: #ffffff;
font-size: 1rem;
padding: .8rem 1.5rem;
cursor: pointer;
}
@media only screen and (max-width: 700px) {
.events-grid .event {
width: 48.5%;
margin: 1% 0 1% 3%;
}
}
@media only screen and (max-width: 480px) {
.events-grid .event {
float: none;
width: 100%;
margin: 0 auto 3%;
}
}
/**************************\
Basic Modal Styles
\**************************/
.modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: 99999;
}
.modal__container {
background-color: #fff;
padding: 30px;
width: 900px;
max-width: 90%;
max-height: 100vh;
border-radius: 4px;
overflow-y: auto;
box-sizing: border-box;
}
.modal__header {
display: flex;
justify-content: space-between;
align-items: center;
}
.modal__title {
margin-top: 0;
margin-bottom: 0;
font-weight: 600;
font-size: 1.25rem;
line-height: 1.25;
color: #000000;
box-sizing: border-box;
}
.modal__close {
background: transparent;
border: 0;
}
.modal__header .modal__close:before {
content: "\2715";
}
.modal__content {
margin-top: 2rem;
margin-bottom: 2rem;
line-height: 1.5;
color: rgba(0, 0, 0, .8);
}
.modal__btn {
font-size: .875rem;
padding-left: 1rem;
padding-right: 1rem;
padding-top: .5rem;
padding-bottom: .5rem;
background-color: #e6e6e6;
color: rgba(0, 0, 0, .8);
border-radius: .25rem;
border-style: none;
border-width: 0;
cursor: pointer;
-webkit-appearance: button;
text-transform: none;
overflow: visible;
line-height: 1.15;
margin: 0;
will-change: transform;
-moz-osx-font-smoothing: grayscale;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform: translateZ(0);
transform: translateZ(0);
transition: -webkit-transform .25s ease-out;
transition: transform .25s ease-out;
transition: transform .25s ease-out, -webkit-transform .25s ease-out;
}
.modal__btn:focus,
.modal__btn:hover {
-webkit-transform: scale(1.05);
transform: scale(1.05);
}
.modal__btn-primary {
background-color: #00449e;
color: #fff;
}
/**************************\
Demo Animation Style
\**************************/
@keyframes mmfadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes mmfadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes mmslideIn {
from {
transform: translateY(15%);
}
to {
transform: translateY(0);
}
}
@keyframes mmslideOut {
from {
transform: translateY(0);
}
to {
transform: translateY(-10%);
}
}
.micromodal-slide {
display: none;
}
.micromodal-slide.is-open {
display: block;
}
.micromodal-slide[aria-hidden="false"] .modal__overlay {
animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="false"] .modal__container {
animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__overlay {
animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__container {
animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide .modal__container,
.micromodal-slide .modal__overlay {
will-change: transform;
}
.micromodal-slide .title {
font-size: 1.5625rem;
color: #111111;
border: none;
font-weight: bold;
text-transform: uppercase;
margin: 0 auto;
padding: 0;
}
.micromodal-slide .images {
margin: .5rem auto 1.5rem;
width: 100%;
height: 350px;
overflow: hidden;
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-ms-border-radius: 10px;
-o-border-radius: 10px;
}
.micromodal-slide .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.micromodal-slide h2.title {
font-size: 1.25rem;
margin: 0 auto .5rem;
}
.micromodal-slide h4.title {
font-size: 1rem;
margin: 0 auto .5rem;
}
.micromodal-slide .short_description {
font-size: 1.125rem;
font-weight: lighter;
}
.micromodal-slide .long_description {
font-size: 1rem;
margin-bottom: 1rem;
}
.micromodal-slide table.period {
border: none;
border-collapse: collapse;
width: 100%;
}
.micromodal-slide table.period td strong {
display: block;
font-size: .8125rem;
}
.micromodal-slide table.period tr {
border-top: 1px solid #dddddd;
}
.micromodal-slide table.period tr.noborder {
border: none;
}
.micromodal-slide table.period td {
border: none;
font-size: 1rem;
vertical-align: top;
padding: 1rem .5rem;
}
.micromodal-slide table.period tr.noborder td {
padding: 0 .5rem 1rem;
}
.micromodal-slide span.tag {
font-size: .8125rem;
padding: 5px 8px;
color: #ffffff;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
.micromodal-slide span.tag.canceled {
background-color: #FF0000;
}
.micromodal-slide span.tag.free-entry {
background-color: #28A745;
}
.micromodal-slide span.tag.limited {
background-color: #1668B2;
}
.micromodal-slide .tags {
margin-right: 1rem;
background-color: #f2f2f2;
font-size: .8125rem;
padding: 5px 8px;
color: #111111;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
File diff suppressed because one or more lines are too long
+434
View File
@@ -0,0 +1,434 @@
.events-list {
list-style: none;
margin: 0 auto;
padding: 0;
}
.events-list .event {
border-bottom: 1px solid #5e5e5e;
margin-bottom: 1rem;
padding: 1rem 0;
display: flex;
gap: 20px;
}
.events-list .event .date {
padding: 1rem 3rem;
}
.events-list .event .date .day,
.events-list .event .date .month {
display: block;
text-align: center;
}
.events-list .event .date .day {
font-size: 2.1875rem;
font-weight: bold;
}
.events-list .event .date .month {}
.events-list .event .title {
margin: 0 auto;
padding: 0;
}
.events-list .event .description {
margin: .5rem auto;
padding: 0;
line-height: 1.3;
}
.events-list .event .images {
width: 200px;
height: 200px;
}
.events-list .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-list .event .event-info {
display: flex;
gap: 20px;
}
.events-grid .event {
float: left;
width: 31.33%;
margin: 1% 0 1% 3%;
border: none !important;
display: block;
}
.events-grid .event:after {
clear: both;
}
.events-grid .event:first-child {
margin-left: 0;
}
.events-grid .event .images {
display: block;
width: 100%;
height: 300px;
margin-bottom: 1rem;
position: relative;
}
.events-grid .event .images .date {
position: absolute;
left: 0;
bottom: 0;
color: #ffffff;
background-color: red;
padding: .8rem 1.5rem;
}
.events-grid .event .date .day {
font-size: 1.875rem;
font-weight: bold;
display: block;
text-align: center;
}
.events-grid .event .date .month {
display: block;
text-align: center;
}
.events-grid .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-grid .event .event-content .title {
font-size: 2rem;
line-height: 1.3;
margin: 0 auto 1rem;
}
.events-grid .event .event-content p {
font-size: .875rem;
line-height: 1.5;
}
.events-grid .event .buttons .btn-cta {
background-color: red;
color: #ffffff;
font-size: 1rem;
padding: .8rem 1.5rem;
cursor: pointer;
}
@media only screen and (max-width: 700px) {
.events-grid .event {
width: 48.5%;
margin: 1% 0 1% 3%;
}
}
@media only screen and (max-width: 480px) {
.events-grid .event {
float: none;
width: 100%;
margin: 0 auto 3%;
}
}
/**************************\
Basic Modal Styles
\**************************/
.modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: 99999;
}
.modal__container {
background-color: #fff;
padding: 30px;
width: 900px;
max-width: 90%;
max-height: 100vh;
border-radius: 4px;
overflow-y: auto;
box-sizing: border-box;
}
.modal__header {
display: flex;
justify-content: space-between;
align-items: center;
}
.modal__title {
margin-top: 0;
margin-bottom: 0;
font-weight: 600;
font-size: 1.25rem;
line-height: 1.25;
color: #000000;
box-sizing: border-box;
}
.modal__close {
background: transparent;
border: 0;
}
.modal__header .modal__close:before {
content: "\2715";
}
.modal__content {
margin-top: 2rem;
margin-bottom: 2rem;
line-height: 1.5;
color: rgba(0, 0, 0, .8);
}
.modal__btn {
font-size: .875rem;
padding-left: 1rem;
padding-right: 1rem;
padding-top: .5rem;
padding-bottom: .5rem;
background-color: #e6e6e6;
color: rgba(0, 0, 0, .8);
border-radius: .25rem;
border-style: none;
border-width: 0;
cursor: pointer;
-webkit-appearance: button;
text-transform: none;
overflow: visible;
line-height: 1.15;
margin: 0;
will-change: transform;
-moz-osx-font-smoothing: grayscale;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform: translateZ(0);
transform: translateZ(0);
transition: -webkit-transform .25s ease-out;
transition: transform .25s ease-out;
transition: transform .25s ease-out, -webkit-transform .25s ease-out;
}
.modal__btn:focus,
.modal__btn:hover {
-webkit-transform: scale(1.05);
transform: scale(1.05);
}
.modal__btn-primary {
background-color: #00449e;
color: #fff;
}
/**************************\
Demo Animation Style
\**************************/
@keyframes mmfadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes mmfadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes mmslideIn {
from {
transform: translateY(15%);
}
to {
transform: translateY(0);
}
}
@keyframes mmslideOut {
from {
transform: translateY(0);
}
to {
transform: translateY(-10%);
}
}
.micromodal-slide {
display: none;
}
.micromodal-slide.is-open {
display: block;
}
.micromodal-slide[aria-hidden="false"] .modal__overlay {
animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="false"] .modal__container {
animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__overlay {
animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__container {
animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide .modal__container,
.micromodal-slide .modal__overlay {
will-change: transform;
}
.micromodal-slide .title {
font-size: 1.5625rem;
color: #111111;
border: none;
font-weight: bold;
text-transform: uppercase;
margin: 0 auto;
padding: 0;
}
.micromodal-slide .images {
margin: .5rem auto 1.5rem;
width: 100%;
height: 350px;
overflow: hidden;
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-ms-border-radius: 10px;
-o-border-radius: 10px;
}
.micromodal-slide .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.micromodal-slide h2.title {
font-size: 1.25rem;
margin: 0 auto .5rem;
}
.micromodal-slide h4.title {
font-size: 1rem;
margin: 0 auto .5rem;
}
.micromodal-slide .short_description {
font-size: 1.125rem;
font-weight: lighter;
}
.micromodal-slide .long_description {
font-size: 1rem;
margin-bottom: 1rem;
}
.micromodal-slide table.period {
border: none;
border-collapse: collapse;
width: 100%;
}
.micromodal-slide table.period td strong {
display: block;
font-size: .8125rem;
}
.micromodal-slide table.period tr {
border-top: 1px solid #dddddd;
}
.micromodal-slide table.period tr.noborder {
border: none;
}
.micromodal-slide table.period td {
border: none;
font-size: 1rem;
vertical-align: top;
padding: 1rem .5rem;
}
.micromodal-slide table.period tr.noborder td {
padding: 0 .5rem 1rem;
}
.micromodal-slide span.tag {
font-size: .8125rem;
padding: 5px 8px;
color: #ffffff;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
.micromodal-slide span.tag.canceled {
background-color: #FF0000;
}
.micromodal-slide span.tag.free-entry {
background-color: #28A745;
}
.micromodal-slide span.tag.limited {
background-color: #1668B2;
}
.micromodal-slide .tags {
margin-right: 1rem;
background-color: #f2f2f2;
font-size: .8125rem;
padding: 5px 8px;
color: #111111;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
Binary file not shown.
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
{"version":"1.0","provider_name":"Rijecki sportski savez","provider_url":"https:\/\/rss.hr","author_name":"RSS","author_url":"https:\/\/rss.hr\/author\/sasa\/","title":"Skup\u0161tina zapisnici","type":"rich","width":600,"height":338,"html":"<blockquote class=\"wp-embedded-content\" data-secret=\"c2FMw3RbmK\"><a href=\"https:\/\/rss.hr\/skupstina-zapisnici\/\">Skup\u0161tina zapisnici<\/a><\/blockquote><iframe sandbox=\"allow-scripts\" security=\"restricted\" src=\"https:\/\/rss.hr\/skupstina-zapisnici\/embed\/#?secret=c2FMw3RbmK\" width=\"600\" height=\"338\" title=\"&#8220;Skup\u0161tina zapisnici&#8221; &#8212; Rijecki sportski savez\" data-secret=\"c2FMw3RbmK\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\" class=\"wp-embedded-content\"><\/iframe><script>\n\/*! This file is auto-generated *\/\n!function(d,l){\"use strict\";l.querySelector&&d.addEventListener&&\"undefined\"!=typeof URL&&(d.wp=d.wp||{},d.wp.receiveEmbedMessage||(d.wp.receiveEmbedMessage=function(e){var t=e.data;if((t||t.secret||t.message||t.value)&&!\/[^a-zA-Z0-9]\/.test(t.secret)){for(var s,r,n,a=l.querySelectorAll('iframe[data-secret=\"'+t.secret+'\"]'),o=l.querySelectorAll('blockquote[data-secret=\"'+t.secret+'\"]'),c=new RegExp(\"^https?:$\",\"i\"),i=0;i<o.length;i++)o[i].style.display=\"none\";for(i=0;i<a.length;i++)s=a[i],e.source===s.contentWindow&&(s.removeAttribute(\"style\"),\"height\"===t.message?(1e3<(r=parseInt(t.value,10))?r=1e3:~~r<200&&(r=200),s.height=r):\"link\"===t.message&&(r=new URL(s.getAttribute(\"src\")),n=new URL(t.value),c.test(n.protocol))&&n.host===r.host&&l.activeElement===s&&(d.top.location.href=t.value))}},d.addEventListener(\"message\",d.wp.receiveEmbedMessage,!1),l.addEventListener(\"DOMContentLoaded\",function(){for(var e,t,s=l.querySelectorAll(\"iframe.wp-embedded-content\"),r=0;r<s.length;r++)(t=(e=s[r]).getAttribute(\"data-secret\"))||(t=Math.random().toString(36).substring(2,12),e.src+=\"#?secret=\"+t,e.setAttribute(\"data-secret\",t)),e.contentWindow.postMessage({message:\"ready\",secret:t},\"*\")},!1)))}(window,document);\n\/\/# sourceURL=https:\/\/rss.hr\/wp-includes\/js\/wp-embed.min.js\n<\/script>\n"}
File diff suppressed because one or more lines are too long
+434
View File
@@ -0,0 +1,434 @@
.events-list {
list-style: none;
margin: 0 auto;
padding: 0;
}
.events-list .event {
border-bottom: 1px solid #5e5e5e;
margin-bottom: 1rem;
padding: 1rem 0;
display: flex;
gap: 20px;
}
.events-list .event .date {
padding: 1rem 3rem;
}
.events-list .event .date .day,
.events-list .event .date .month {
display: block;
text-align: center;
}
.events-list .event .date .day {
font-size: 2.1875rem;
font-weight: bold;
}
.events-list .event .date .month {}
.events-list .event .title {
margin: 0 auto;
padding: 0;
}
.events-list .event .description {
margin: .5rem auto;
padding: 0;
line-height: 1.3;
}
.events-list .event .images {
width: 200px;
height: 200px;
}
.events-list .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-list .event .event-info {
display: flex;
gap: 20px;
}
.events-grid .event {
float: left;
width: 31.33%;
margin: 1% 0 1% 3%;
border: none !important;
display: block;
}
.events-grid .event:after {
clear: both;
}
.events-grid .event:first-child {
margin-left: 0;
}
.events-grid .event .images {
display: block;
width: 100%;
height: 300px;
margin-bottom: 1rem;
position: relative;
}
.events-grid .event .images .date {
position: absolute;
left: 0;
bottom: 0;
color: #ffffff;
background-color: red;
padding: .8rem 1.5rem;
}
.events-grid .event .date .day {
font-size: 1.875rem;
font-weight: bold;
display: block;
text-align: center;
}
.events-grid .event .date .month {
display: block;
text-align: center;
}
.events-grid .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-grid .event .event-content .title {
font-size: 2rem;
line-height: 1.3;
margin: 0 auto 1rem;
}
.events-grid .event .event-content p {
font-size: .875rem;
line-height: 1.5;
}
.events-grid .event .buttons .btn-cta {
background-color: red;
color: #ffffff;
font-size: 1rem;
padding: .8rem 1.5rem;
cursor: pointer;
}
@media only screen and (max-width: 700px) {
.events-grid .event {
width: 48.5%;
margin: 1% 0 1% 3%;
}
}
@media only screen and (max-width: 480px) {
.events-grid .event {
float: none;
width: 100%;
margin: 0 auto 3%;
}
}
/**************************\
Basic Modal Styles
\**************************/
.modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: 99999;
}
.modal__container {
background-color: #fff;
padding: 30px;
width: 900px;
max-width: 90%;
max-height: 100vh;
border-radius: 4px;
overflow-y: auto;
box-sizing: border-box;
}
.modal__header {
display: flex;
justify-content: space-between;
align-items: center;
}
.modal__title {
margin-top: 0;
margin-bottom: 0;
font-weight: 600;
font-size: 1.25rem;
line-height: 1.25;
color: #000000;
box-sizing: border-box;
}
.modal__close {
background: transparent;
border: 0;
}
.modal__header .modal__close:before {
content: "\2715";
}
.modal__content {
margin-top: 2rem;
margin-bottom: 2rem;
line-height: 1.5;
color: rgba(0, 0, 0, .8);
}
.modal__btn {
font-size: .875rem;
padding-left: 1rem;
padding-right: 1rem;
padding-top: .5rem;
padding-bottom: .5rem;
background-color: #e6e6e6;
color: rgba(0, 0, 0, .8);
border-radius: .25rem;
border-style: none;
border-width: 0;
cursor: pointer;
-webkit-appearance: button;
text-transform: none;
overflow: visible;
line-height: 1.15;
margin: 0;
will-change: transform;
-moz-osx-font-smoothing: grayscale;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform: translateZ(0);
transform: translateZ(0);
transition: -webkit-transform .25s ease-out;
transition: transform .25s ease-out;
transition: transform .25s ease-out, -webkit-transform .25s ease-out;
}
.modal__btn:focus,
.modal__btn:hover {
-webkit-transform: scale(1.05);
transform: scale(1.05);
}
.modal__btn-primary {
background-color: #00449e;
color: #fff;
}
/**************************\
Demo Animation Style
\**************************/
@keyframes mmfadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes mmfadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes mmslideIn {
from {
transform: translateY(15%);
}
to {
transform: translateY(0);
}
}
@keyframes mmslideOut {
from {
transform: translateY(0);
}
to {
transform: translateY(-10%);
}
}
.micromodal-slide {
display: none;
}
.micromodal-slide.is-open {
display: block;
}
.micromodal-slide[aria-hidden="false"] .modal__overlay {
animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="false"] .modal__container {
animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__overlay {
animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__container {
animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide .modal__container,
.micromodal-slide .modal__overlay {
will-change: transform;
}
.micromodal-slide .title {
font-size: 1.5625rem;
color: #111111;
border: none;
font-weight: bold;
text-transform: uppercase;
margin: 0 auto;
padding: 0;
}
.micromodal-slide .images {
margin: .5rem auto 1.5rem;
width: 100%;
height: 350px;
overflow: hidden;
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-ms-border-radius: 10px;
-o-border-radius: 10px;
}
.micromodal-slide .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.micromodal-slide h2.title {
font-size: 1.25rem;
margin: 0 auto .5rem;
}
.micromodal-slide h4.title {
font-size: 1rem;
margin: 0 auto .5rem;
}
.micromodal-slide .short_description {
font-size: 1.125rem;
font-weight: lighter;
}
.micromodal-slide .long_description {
font-size: 1rem;
margin-bottom: 1rem;
}
.micromodal-slide table.period {
border: none;
border-collapse: collapse;
width: 100%;
}
.micromodal-slide table.period td strong {
display: block;
font-size: .8125rem;
}
.micromodal-slide table.period tr {
border-top: 1px solid #dddddd;
}
.micromodal-slide table.period tr.noborder {
border: none;
}
.micromodal-slide table.period td {
border: none;
font-size: 1rem;
vertical-align: top;
padding: 1rem .5rem;
}
.micromodal-slide table.period tr.noborder td {
padding: 0 .5rem 1rem;
}
.micromodal-slide span.tag {
font-size: .8125rem;
padding: 5px 8px;
color: #ffffff;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
.micromodal-slide span.tag.canceled {
background-color: #FF0000;
}
.micromodal-slide span.tag.free-entry {
background-color: #28A745;
}
.micromodal-slide span.tag.limited {
background-color: #1668B2;
}
.micromodal-slide .tags {
margin-right: 1rem;
background-color: #f2f2f2;
font-size: .8125rem;
padding: 5px 8px;
color: #111111;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
Binary file not shown.
Binary file not shown.
+434
View File
@@ -0,0 +1,434 @@
.events-list {
list-style: none;
margin: 0 auto;
padding: 0;
}
.events-list .event {
border-bottom: 1px solid #5e5e5e;
margin-bottom: 1rem;
padding: 1rem 0;
display: flex;
gap: 20px;
}
.events-list .event .date {
padding: 1rem 3rem;
}
.events-list .event .date .day,
.events-list .event .date .month {
display: block;
text-align: center;
}
.events-list .event .date .day {
font-size: 2.1875rem;
font-weight: bold;
}
.events-list .event .date .month {}
.events-list .event .title {
margin: 0 auto;
padding: 0;
}
.events-list .event .description {
margin: .5rem auto;
padding: 0;
line-height: 1.3;
}
.events-list .event .images {
width: 200px;
height: 200px;
}
.events-list .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-list .event .event-info {
display: flex;
gap: 20px;
}
.events-grid .event {
float: left;
width: 31.33%;
margin: 1% 0 1% 3%;
border: none !important;
display: block;
}
.events-grid .event:after {
clear: both;
}
.events-grid .event:first-child {
margin-left: 0;
}
.events-grid .event .images {
display: block;
width: 100%;
height: 300px;
margin-bottom: 1rem;
position: relative;
}
.events-grid .event .images .date {
position: absolute;
left: 0;
bottom: 0;
color: #ffffff;
background-color: red;
padding: .8rem 1.5rem;
}
.events-grid .event .date .day {
font-size: 1.875rem;
font-weight: bold;
display: block;
text-align: center;
}
.events-grid .event .date .month {
display: block;
text-align: center;
}
.events-grid .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-grid .event .event-content .title {
font-size: 2rem;
line-height: 1.3;
margin: 0 auto 1rem;
}
.events-grid .event .event-content p {
font-size: .875rem;
line-height: 1.5;
}
.events-grid .event .buttons .btn-cta {
background-color: red;
color: #ffffff;
font-size: 1rem;
padding: .8rem 1.5rem;
cursor: pointer;
}
@media only screen and (max-width: 700px) {
.events-grid .event {
width: 48.5%;
margin: 1% 0 1% 3%;
}
}
@media only screen and (max-width: 480px) {
.events-grid .event {
float: none;
width: 100%;
margin: 0 auto 3%;
}
}
/**************************\
Basic Modal Styles
\**************************/
.modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: 99999;
}
.modal__container {
background-color: #fff;
padding: 30px;
width: 900px;
max-width: 90%;
max-height: 100vh;
border-radius: 4px;
overflow-y: auto;
box-sizing: border-box;
}
.modal__header {
display: flex;
justify-content: space-between;
align-items: center;
}
.modal__title {
margin-top: 0;
margin-bottom: 0;
font-weight: 600;
font-size: 1.25rem;
line-height: 1.25;
color: #000000;
box-sizing: border-box;
}
.modal__close {
background: transparent;
border: 0;
}
.modal__header .modal__close:before {
content: "\2715";
}
.modal__content {
margin-top: 2rem;
margin-bottom: 2rem;
line-height: 1.5;
color: rgba(0, 0, 0, .8);
}
.modal__btn {
font-size: .875rem;
padding-left: 1rem;
padding-right: 1rem;
padding-top: .5rem;
padding-bottom: .5rem;
background-color: #e6e6e6;
color: rgba(0, 0, 0, .8);
border-radius: .25rem;
border-style: none;
border-width: 0;
cursor: pointer;
-webkit-appearance: button;
text-transform: none;
overflow: visible;
line-height: 1.15;
margin: 0;
will-change: transform;
-moz-osx-font-smoothing: grayscale;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform: translateZ(0);
transform: translateZ(0);
transition: -webkit-transform .25s ease-out;
transition: transform .25s ease-out;
transition: transform .25s ease-out, -webkit-transform .25s ease-out;
}
.modal__btn:focus,
.modal__btn:hover {
-webkit-transform: scale(1.05);
transform: scale(1.05);
}
.modal__btn-primary {
background-color: #00449e;
color: #fff;
}
/**************************\
Demo Animation Style
\**************************/
@keyframes mmfadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes mmfadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes mmslideIn {
from {
transform: translateY(15%);
}
to {
transform: translateY(0);
}
}
@keyframes mmslideOut {
from {
transform: translateY(0);
}
to {
transform: translateY(-10%);
}
}
.micromodal-slide {
display: none;
}
.micromodal-slide.is-open {
display: block;
}
.micromodal-slide[aria-hidden="false"] .modal__overlay {
animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="false"] .modal__container {
animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__overlay {
animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__container {
animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide .modal__container,
.micromodal-slide .modal__overlay {
will-change: transform;
}
.micromodal-slide .title {
font-size: 1.5625rem;
color: #111111;
border: none;
font-weight: bold;
text-transform: uppercase;
margin: 0 auto;
padding: 0;
}
.micromodal-slide .images {
margin: .5rem auto 1.5rem;
width: 100%;
height: 350px;
overflow: hidden;
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-ms-border-radius: 10px;
-o-border-radius: 10px;
}
.micromodal-slide .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.micromodal-slide h2.title {
font-size: 1.25rem;
margin: 0 auto .5rem;
}
.micromodal-slide h4.title {
font-size: 1rem;
margin: 0 auto .5rem;
}
.micromodal-slide .short_description {
font-size: 1.125rem;
font-weight: lighter;
}
.micromodal-slide .long_description {
font-size: 1rem;
margin-bottom: 1rem;
}
.micromodal-slide table.period {
border: none;
border-collapse: collapse;
width: 100%;
}
.micromodal-slide table.period td strong {
display: block;
font-size: .8125rem;
}
.micromodal-slide table.period tr {
border-top: 1px solid #dddddd;
}
.micromodal-slide table.period tr.noborder {
border: none;
}
.micromodal-slide table.period td {
border: none;
font-size: 1rem;
vertical-align: top;
padding: 1rem .5rem;
}
.micromodal-slide table.period tr.noborder td {
padding: 0 .5rem 1rem;
}
.micromodal-slide span.tag {
font-size: .8125rem;
padding: 5px 8px;
color: #ffffff;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
.micromodal-slide span.tag.canceled {
background-color: #FF0000;
}
.micromodal-slide span.tag.free-entry {
background-color: #28A745;
}
.micromodal-slide span.tag.limited {
background-color: #1668B2;
}
.micromodal-slide .tags {
margin-right: 1rem;
background-color: #f2f2f2;
font-size: .8125rem;
padding: 5px 8px;
color: #111111;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+434
View File
@@ -0,0 +1,434 @@
.events-list {
list-style: none;
margin: 0 auto;
padding: 0;
}
.events-list .event {
border-bottom: 1px solid #5e5e5e;
margin-bottom: 1rem;
padding: 1rem 0;
display: flex;
gap: 20px;
}
.events-list .event .date {
padding: 1rem 3rem;
}
.events-list .event .date .day,
.events-list .event .date .month {
display: block;
text-align: center;
}
.events-list .event .date .day {
font-size: 2.1875rem;
font-weight: bold;
}
.events-list .event .date .month {}
.events-list .event .title {
margin: 0 auto;
padding: 0;
}
.events-list .event .description {
margin: .5rem auto;
padding: 0;
line-height: 1.3;
}
.events-list .event .images {
width: 200px;
height: 200px;
}
.events-list .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-list .event .event-info {
display: flex;
gap: 20px;
}
.events-grid .event {
float: left;
width: 31.33%;
margin: 1% 0 1% 3%;
border: none !important;
display: block;
}
.events-grid .event:after {
clear: both;
}
.events-grid .event:first-child {
margin-left: 0;
}
.events-grid .event .images {
display: block;
width: 100%;
height: 300px;
margin-bottom: 1rem;
position: relative;
}
.events-grid .event .images .date {
position: absolute;
left: 0;
bottom: 0;
color: #ffffff;
background-color: red;
padding: .8rem 1.5rem;
}
.events-grid .event .date .day {
font-size: 1.875rem;
font-weight: bold;
display: block;
text-align: center;
}
.events-grid .event .date .month {
display: block;
text-align: center;
}
.events-grid .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-grid .event .event-content .title {
font-size: 2rem;
line-height: 1.3;
margin: 0 auto 1rem;
}
.events-grid .event .event-content p {
font-size: .875rem;
line-height: 1.5;
}
.events-grid .event .buttons .btn-cta {
background-color: red;
color: #ffffff;
font-size: 1rem;
padding: .8rem 1.5rem;
cursor: pointer;
}
@media only screen and (max-width: 700px) {
.events-grid .event {
width: 48.5%;
margin: 1% 0 1% 3%;
}
}
@media only screen and (max-width: 480px) {
.events-grid .event {
float: none;
width: 100%;
margin: 0 auto 3%;
}
}
/**************************\
Basic Modal Styles
\**************************/
.modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: 99999;
}
.modal__container {
background-color: #fff;
padding: 30px;
width: 900px;
max-width: 90%;
max-height: 100vh;
border-radius: 4px;
overflow-y: auto;
box-sizing: border-box;
}
.modal__header {
display: flex;
justify-content: space-between;
align-items: center;
}
.modal__title {
margin-top: 0;
margin-bottom: 0;
font-weight: 600;
font-size: 1.25rem;
line-height: 1.25;
color: #000000;
box-sizing: border-box;
}
.modal__close {
background: transparent;
border: 0;
}
.modal__header .modal__close:before {
content: "\2715";
}
.modal__content {
margin-top: 2rem;
margin-bottom: 2rem;
line-height: 1.5;
color: rgba(0, 0, 0, .8);
}
.modal__btn {
font-size: .875rem;
padding-left: 1rem;
padding-right: 1rem;
padding-top: .5rem;
padding-bottom: .5rem;
background-color: #e6e6e6;
color: rgba(0, 0, 0, .8);
border-radius: .25rem;
border-style: none;
border-width: 0;
cursor: pointer;
-webkit-appearance: button;
text-transform: none;
overflow: visible;
line-height: 1.15;
margin: 0;
will-change: transform;
-moz-osx-font-smoothing: grayscale;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform: translateZ(0);
transform: translateZ(0);
transition: -webkit-transform .25s ease-out;
transition: transform .25s ease-out;
transition: transform .25s ease-out, -webkit-transform .25s ease-out;
}
.modal__btn:focus,
.modal__btn:hover {
-webkit-transform: scale(1.05);
transform: scale(1.05);
}
.modal__btn-primary {
background-color: #00449e;
color: #fff;
}
/**************************\
Demo Animation Style
\**************************/
@keyframes mmfadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes mmfadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes mmslideIn {
from {
transform: translateY(15%);
}
to {
transform: translateY(0);
}
}
@keyframes mmslideOut {
from {
transform: translateY(0);
}
to {
transform: translateY(-10%);
}
}
.micromodal-slide {
display: none;
}
.micromodal-slide.is-open {
display: block;
}
.micromodal-slide[aria-hidden="false"] .modal__overlay {
animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="false"] .modal__container {
animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__overlay {
animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__container {
animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide .modal__container,
.micromodal-slide .modal__overlay {
will-change: transform;
}
.micromodal-slide .title {
font-size: 1.5625rem;
color: #111111;
border: none;
font-weight: bold;
text-transform: uppercase;
margin: 0 auto;
padding: 0;
}
.micromodal-slide .images {
margin: .5rem auto 1.5rem;
width: 100%;
height: 350px;
overflow: hidden;
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-ms-border-radius: 10px;
-o-border-radius: 10px;
}
.micromodal-slide .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.micromodal-slide h2.title {
font-size: 1.25rem;
margin: 0 auto .5rem;
}
.micromodal-slide h4.title {
font-size: 1rem;
margin: 0 auto .5rem;
}
.micromodal-slide .short_description {
font-size: 1.125rem;
font-weight: lighter;
}
.micromodal-slide .long_description {
font-size: 1rem;
margin-bottom: 1rem;
}
.micromodal-slide table.period {
border: none;
border-collapse: collapse;
width: 100%;
}
.micromodal-slide table.period td strong {
display: block;
font-size: .8125rem;
}
.micromodal-slide table.period tr {
border-top: 1px solid #dddddd;
}
.micromodal-slide table.period tr.noborder {
border: none;
}
.micromodal-slide table.period td {
border: none;
font-size: 1rem;
vertical-align: top;
padding: 1rem .5rem;
}
.micromodal-slide table.period tr.noborder td {
padding: 0 .5rem 1rem;
}
.micromodal-slide span.tag {
font-size: .8125rem;
padding: 5px 8px;
color: #ffffff;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
.micromodal-slide span.tag.canceled {
background-color: #FF0000;
}
.micromodal-slide span.tag.free-entry {
background-color: #28A745;
}
.micromodal-slide span.tag.limited {
background-color: #1668B2;
}
.micromodal-slide .tags {
margin-right: 1rem;
background-color: #f2f2f2;
font-size: .8125rem;
padding: 5px 8px;
color: #111111;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
+1
View File
@@ -0,0 +1 @@
.elementor-4568 .elementor-element.elementor-element-5a7d978d:not(.elementor-motion-effects-element-type-background), .elementor-4568 .elementor-element.elementor-element-5a7d978d > .elementor-motion-effects-container > .elementor-motion-effects-layer{background-color:#FFFFFF;background-image:url("https://rss.hr/wp-content/uploads/2021/08/Passport-Cover.png");background-position:bottom center;background-size:cover;}.elementor-4568 .elementor-element.elementor-element-5a7d978d > .elementor-background-overlay{background-color:transparent;background-image:linear-gradient(180deg, #153243 0%, #192D31 100%);opacity:0.6;transition:background 0.3s, border-radius 0.3s, opacity 0.3s;}.elementor-4568 .elementor-element.elementor-element-5a7d978d > .elementor-container{min-height:300px;}.elementor-4568 .elementor-element.elementor-element-5a7d978d{transition:background 0.3s, border 0.3s, border-radius 0.3s, box-shadow 0.3s;padding:200px 0px 100px 0px;}.elementor-4568 .elementor-element.elementor-element-36e3182{text-align:center;}.elementor-4568 .elementor-element.elementor-element-36e3182 .elementor-heading-title{letter-spacing:2.5px;color:#FFFFFF;}.elementor-4568 .elementor-element.elementor-element-beaccb7{--spacer-size:10px;}.elementor-4568 .elementor-element.elementor-element-3db69b3 .elementor-toggle-title{font-weight:bold;}.elementor-4568 .elementor-element.elementor-element-3db69b3 .elementor-tab-content{text-transform:none;}.elementor-4568 .elementor-element.elementor-element-f9184cf{text-align:start;}.elementor-4568 .elementor-element.elementor-element-6f65952{margin-top:0px;margin-bottom:0px;padding:0px 0px 0px 0px;}.elementor-4568 .elementor-element.elementor-element-37512b3{text-align:start;}.elementor-4568 .elementor-element.elementor-element-37512b3 img{width:18%;max-width:18%;height:192px;object-fit:contain;object-position:center center;}.elementor-4568 .elementor-element.elementor-element-92dcbc4{margin-top:0px;margin-bottom:0px;padding:0px 0px 0px 0px;}.elementor-4568 .elementor-element.elementor-element-ff004fa > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-4568 .elementor-element.elementor-element-ff004fa{text-align:center;}.elementor-4568 .elementor-element.elementor-element-ff004fa img{width:40%;max-width:40%;height:155px;object-fit:contain;object-position:center center;border-radius:0px 0px 0px 0px;}body.elementor-page-4568:not(.elementor-motion-effects-element-type-background), body.elementor-page-4568 > .elementor-motion-effects-container > .elementor-motion-effects-layer{background-color:#ffffff;}@media(min-width:1025px){.elementor-4568 .elementor-element.elementor-element-5a7d978d:not(.elementor-motion-effects-element-type-background), .elementor-4568 .elementor-element.elementor-element-5a7d978d > .elementor-motion-effects-container > .elementor-motion-effects-layer{background-attachment:fixed;}}@media(max-width:1024px){.elementor-4568 .elementor-element.elementor-element-5a7d978d{padding:140px 80px 80px 80px;}.elementor-4568 .elementor-element.elementor-element-36e3182 .elementor-heading-title{font-size:40px;}.elementor-4568 .elementor-element.elementor-element-37512b3 img{width:27%;max-width:27%;}.elementor-4568 .elementor-element.elementor-element-ff004fa img{width:50%;max-width:50%;}}@media(max-width:767px){.elementor-4568 .elementor-element.elementor-element-5a7d978d{padding:0px 0px 0px 0px;}.elementor-4568 .elementor-element.elementor-element-7b7c0473{width:100%;}.elementor-4568 .elementor-element.elementor-element-36e3182 .elementor-heading-title{font-size:30px;}.elementor-4568 .elementor-element.elementor-element-37512b3{text-align:center;}.elementor-4568 .elementor-element.elementor-element-37512b3 img{width:60%;max-width:60%;}.elementor-4568 .elementor-element.elementor-element-ff004fa{text-align:center;}.elementor-4568 .elementor-element.elementor-element-ff004fa img{width:70%;max-width:70%;height:61px;}}
+434
View File
@@ -0,0 +1,434 @@
.events-list {
list-style: none;
margin: 0 auto;
padding: 0;
}
.events-list .event {
border-bottom: 1px solid #5e5e5e;
margin-bottom: 1rem;
padding: 1rem 0;
display: flex;
gap: 20px;
}
.events-list .event .date {
padding: 1rem 3rem;
}
.events-list .event .date .day,
.events-list .event .date .month {
display: block;
text-align: center;
}
.events-list .event .date .day {
font-size: 2.1875rem;
font-weight: bold;
}
.events-list .event .date .month {}
.events-list .event .title {
margin: 0 auto;
padding: 0;
}
.events-list .event .description {
margin: .5rem auto;
padding: 0;
line-height: 1.3;
}
.events-list .event .images {
width: 200px;
height: 200px;
}
.events-list .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-list .event .event-info {
display: flex;
gap: 20px;
}
.events-grid .event {
float: left;
width: 31.33%;
margin: 1% 0 1% 3%;
border: none !important;
display: block;
}
.events-grid .event:after {
clear: both;
}
.events-grid .event:first-child {
margin-left: 0;
}
.events-grid .event .images {
display: block;
width: 100%;
height: 300px;
margin-bottom: 1rem;
position: relative;
}
.events-grid .event .images .date {
position: absolute;
left: 0;
bottom: 0;
color: #ffffff;
background-color: red;
padding: .8rem 1.5rem;
}
.events-grid .event .date .day {
font-size: 1.875rem;
font-weight: bold;
display: block;
text-align: center;
}
.events-grid .event .date .month {
display: block;
text-align: center;
}
.events-grid .event .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.events-grid .event .event-content .title {
font-size: 2rem;
line-height: 1.3;
margin: 0 auto 1rem;
}
.events-grid .event .event-content p {
font-size: .875rem;
line-height: 1.5;
}
.events-grid .event .buttons .btn-cta {
background-color: red;
color: #ffffff;
font-size: 1rem;
padding: .8rem 1.5rem;
cursor: pointer;
}
@media only screen and (max-width: 700px) {
.events-grid .event {
width: 48.5%;
margin: 1% 0 1% 3%;
}
}
@media only screen and (max-width: 480px) {
.events-grid .event {
float: none;
width: 100%;
margin: 0 auto 3%;
}
}
/**************************\
Basic Modal Styles
\**************************/
.modal__overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
justify-content: center;
align-items: center;
z-index: 99999;
}
.modal__container {
background-color: #fff;
padding: 30px;
width: 900px;
max-width: 90%;
max-height: 100vh;
border-radius: 4px;
overflow-y: auto;
box-sizing: border-box;
}
.modal__header {
display: flex;
justify-content: space-between;
align-items: center;
}
.modal__title {
margin-top: 0;
margin-bottom: 0;
font-weight: 600;
font-size: 1.25rem;
line-height: 1.25;
color: #000000;
box-sizing: border-box;
}
.modal__close {
background: transparent;
border: 0;
}
.modal__header .modal__close:before {
content: "\2715";
}
.modal__content {
margin-top: 2rem;
margin-bottom: 2rem;
line-height: 1.5;
color: rgba(0, 0, 0, .8);
}
.modal__btn {
font-size: .875rem;
padding-left: 1rem;
padding-right: 1rem;
padding-top: .5rem;
padding-bottom: .5rem;
background-color: #e6e6e6;
color: rgba(0, 0, 0, .8);
border-radius: .25rem;
border-style: none;
border-width: 0;
cursor: pointer;
-webkit-appearance: button;
text-transform: none;
overflow: visible;
line-height: 1.15;
margin: 0;
will-change: transform;
-moz-osx-font-smoothing: grayscale;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform: translateZ(0);
transform: translateZ(0);
transition: -webkit-transform .25s ease-out;
transition: transform .25s ease-out;
transition: transform .25s ease-out, -webkit-transform .25s ease-out;
}
.modal__btn:focus,
.modal__btn:hover {
-webkit-transform: scale(1.05);
transform: scale(1.05);
}
.modal__btn-primary {
background-color: #00449e;
color: #fff;
}
/**************************\
Demo Animation Style
\**************************/
@keyframes mmfadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes mmfadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes mmslideIn {
from {
transform: translateY(15%);
}
to {
transform: translateY(0);
}
}
@keyframes mmslideOut {
from {
transform: translateY(0);
}
to {
transform: translateY(-10%);
}
}
.micromodal-slide {
display: none;
}
.micromodal-slide.is-open {
display: block;
}
.micromodal-slide[aria-hidden="false"] .modal__overlay {
animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="false"] .modal__container {
animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__overlay {
animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1);
}
.micromodal-slide[aria-hidden="true"] .modal__container {
animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1);
}
.micromodal-slide .modal__container,
.micromodal-slide .modal__overlay {
will-change: transform;
}
.micromodal-slide .title {
font-size: 1.5625rem;
color: #111111;
border: none;
font-weight: bold;
text-transform: uppercase;
margin: 0 auto;
padding: 0;
}
.micromodal-slide .images {
margin: .5rem auto 1.5rem;
width: 100%;
height: 350px;
overflow: hidden;
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-ms-border-radius: 10px;
-o-border-radius: 10px;
}
.micromodal-slide .images img {
width: 100%;
height: 100%;
object-fit: cover;
}
.micromodal-slide h2.title {
font-size: 1.25rem;
margin: 0 auto .5rem;
}
.micromodal-slide h4.title {
font-size: 1rem;
margin: 0 auto .5rem;
}
.micromodal-slide .short_description {
font-size: 1.125rem;
font-weight: lighter;
}
.micromodal-slide .long_description {
font-size: 1rem;
margin-bottom: 1rem;
}
.micromodal-slide table.period {
border: none;
border-collapse: collapse;
width: 100%;
}
.micromodal-slide table.period td strong {
display: block;
font-size: .8125rem;
}
.micromodal-slide table.period tr {
border-top: 1px solid #dddddd;
}
.micromodal-slide table.period tr.noborder {
border: none;
}
.micromodal-slide table.period td {
border: none;
font-size: 1rem;
vertical-align: top;
padding: 1rem .5rem;
}
.micromodal-slide table.period tr.noborder td {
padding: 0 .5rem 1rem;
}
.micromodal-slide span.tag {
font-size: .8125rem;
padding: 5px 8px;
color: #ffffff;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
.micromodal-slide span.tag.canceled {
background-color: #FF0000;
}
.micromodal-slide span.tag.free-entry {
background-color: #28A745;
}
.micromodal-slide span.tag.limited {
background-color: #1668B2;
}
.micromodal-slide .tags {
margin-right: 1rem;
background-color: #f2f2f2;
font-size: .8125rem;
padding: 5px 8px;
color: #111111;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
-ms-border-radius: 3px;
-o-border-radius: 3px;
}
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More