diff --git a/.DS_Store b/.DS_Store index 89d5114..e90cb3b 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/App.tsx b/App.tsx index abba0e9..fae975c 100644 --- a/App.tsx +++ b/App.tsx @@ -15,6 +15,10 @@ import { getEnvVar } from './utils/envUtils'; import { AUTHOR_CONFIG } from './_EDITABLE_CONFIG/author'; import { UI_TEXT } from './_EDITABLE_CONFIG/ui_text'; +// --- ASSET IMPORT --- +// Importujemy logo bezpośrednio, aby bundler poprawnie rozwiązał ścieżkę +import logoImg from './logo.png'; + const STORAGE_KEY = 'gpx-storyteller-state-v6'; const AUTH_KEY = 'promptstory-auth-token'; @@ -131,7 +135,7 @@ const App: React.FC = () => { const [isLoaded, setIsLoaded] = useState(false); const [errorMessage, setErrorMessage] = useState(null); - // LOGO & AVATAR STATE: Default to false (no error), so we try to show image first. + // LOGO & AVATAR STATE const [logoError, setLogoError] = useState(false); const [avatarError, setAvatarError] = useState(false); @@ -245,10 +249,10 @@ const App: React.FC = () => {
- {/* LOGO LOGIC: Try to show image. If error, fallback to icon/text. */} + {/* LOGO LOGIC: Use imported logoImg */} {!logoError ? ( Logo setLogoError(true)} className="h-10 object-contain" diff --git a/_EDITABLE_CONFIG/author.ts b/_EDITABLE_CONFIG/author.ts index 11f3739..9fd58da 100644 --- a/_EDITABLE_CONFIG/author.ts +++ b/_EDITABLE_CONFIG/author.ts @@ -1,10 +1,13 @@ +// Import obrazu, aby bundler go dołączył +import avatarImg from '../avatar.jpeg'; + export const AUTHOR_CONFIG = { - name: "Arkadiusz AreBynd[] Bykowski", - description: "Zamieniam Twoją stronę w maszynkę do zarabiania pieniędzy. Od 12 lat projektuję strony, które sprzedają bez Twojego udziału – póki Ty śpisz, one robią za Ciebie robotę. Specjalizuję się w landing page'ach, które budują zaufanie i zmieniają odwiedzających w płacących klientów.", + name: "Arkadiusz Bykowski", + description: "Zamieniam Twoją stronę w maszynkę do zarabiania pieniędzy. Od 12 lat projektuję strony, które sprzedają bez Twojego udziału.", tags: ["Product Design", "AI Automation", "No-Code"], websiteUrl: "https://bykowski.pro/", websiteLabel: "Odwiedź stronę", - // Nazwa pliku w folderze public - avatarImage: "avatar.jpeg" + // Używamy zaimportowanego obrazu + avatarImage: avatarImg }; diff --git a/components/StepData.tsx b/components/StepData.tsx index b9a7232..e69de29 100644 --- a/components/StepData.tsx +++ b/components/StepData.tsx @@ -1 +0,0 @@ -// Unused. Logic moved to StepDetails.tsx \ No newline at end of file diff --git a/utils/gpxUtils.ts b/utils/gpxUtils.ts index e1765a9..eb1ce70 100644 --- a/utils/gpxUtils.ts +++ b/utils/gpxUtils.ts @@ -16,10 +16,15 @@ export const parseGpxFile = async (file: File): Promise => { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(text, 'text/xml'); - const trkpts = Array.from(xmlDoc.getElementsByTagName('trkpt')); + // Obsługa różnych wariantów tagu 'trkpt' (z namespace lub bez) + let trkpts = Array.from(xmlDoc.getElementsByTagName('trkpt')); + if (trkpts.length === 0) { + // Spróbuj znaleźć po localName, jeśli namespace'y przeszkadzają + const allElements = xmlDoc.getElementsByTagName('*'); + trkpts = Array.from(allElements).filter(el => el.localName === 'trkpt'); + } if (trkpts.length === 0) { - // Fallback logic could be added here for routes without time, but for storytelling we focus on tracks throw new Error('Nie znaleziono punktów śledzenia (trkpt) w pliku GPX.'); } @@ -51,44 +56,62 @@ export const parseGpxFile = async (file: File): Promise => { let lastLat: number | null = null; let lastLon: number | null = null; let lastEle: number | null = null; - let lastTime: number | null = null; + + // Helper to get text content from child nodes loosely + const getChildText = (parent: Element, tagName: string): string | null => { + let el = parent.getElementsByTagName(tagName)[0]; + if (!el) { + // Try finding by localName if strictly not found + for(let i=0; i 0) { - totalHr += hr; - hrCount++; - if (hr > maxHr) maxHr = hr; - currentSplitHrSum += hr; - currentSplitHrCount++; - } - } - if (cadNode?.textContent) { - cad = parseInt(cadNode.textContent); - if (!isNaN(cad) && cad > 0) { - totalCadence += cad; - cadenceCount++; - } - } + + for (let j = 0; j < allChilds.length; j++) { + const node = allChilds[j]; + const name = node.localName.toLowerCase(); + if (name === 'hr' || name === 'heartrate') { + const val = parseInt(node.textContent || '0'); + if (!isNaN(val) && val > 0) hr = val; + } + if (name === 'cad' || name === 'cadence') { + const val = parseInt(node.textContent || '0'); + if (!isNaN(val) && val > 0) cad = val; + } + } + + if (hr > 0) { + totalHr += hr; + hrCount++; + if (hr > maxHr) maxHr = hr; + currentSplitHrSum += hr; + currentSplitHrCount++; + } + if (cad > 0) { + totalCadence += cad; + cadenceCount++; } let currentTime = 0; @@ -117,8 +140,11 @@ export const parseGpxFile = async (file: File): Promise => { // Elevation Gain if (lastEle !== null && !isNaN(ele) && ele > lastEle) { const gain = ele - lastEle; - totalElevationGain += gain; - currentSplitElevGain += gain; + // Ignorujemy mikro-zmiany (szum GPS) poniżej 0.5m + if (gain > 0.2) { + totalElevationGain += gain; + currentSplitElevGain += gain; + } } // --- SPLIT LOGIC (Co 1 KM) --- @@ -133,7 +159,7 @@ export const parseGpxFile = async (file: File): Promise => { // Reset Split splitIndex++; - currentSplitDistance = 0; // or subtract 1.0 specifically for precision, but reset is safer for GPS drift + currentSplitDistance = currentSplitDistance - 1.0; currentSplitTimeStart = currentTime; currentSplitHrSum = 0; currentSplitHrCount = 0; @@ -144,7 +170,6 @@ export const parseGpxFile = async (file: File): Promise => { lastLat = lat; lastLon = lon; lastEle = isNaN(ele) ? lastEle : ele; - lastTime = currentTime; } // --- PODSUMOWANIE --- @@ -157,7 +182,7 @@ export const parseGpxFile = async (file: File): Promise => { const uiStats: ActivityStats = { distance: `${totalDistanceKm.toFixed(2)} km`, duration: msToTime(totalDurationMs), - elevation: `${Math.round(totalElevationGain)}m (Max: ${Math.round(maxElevation)}m)` + elevation: `${Math.round(totalElevationGain)}m (Max: ${Math.round(maxElevation === -Infinity ? 0 : maxElevation)}m)` }; // --- BUDOWANIE BOGATEGO RAPORTU DLA AI --- @@ -166,7 +191,7 @@ export const parseGpxFile = async (file: File): Promise => { report += `- Dystans Całkowity: ${totalDistanceKm.toFixed(2)} km\n`; report += `- Czas Całkowity: ${msToTime(totalDurationMs)}\n`; report += `- Średnie Tempo: ${formatPace(avgPaceMs)} min/km\n`; - report += `- Przewyższenia: +${Math.round(totalElevationGain)}m / Max Wysokość: ${Math.round(maxElevation)}m\n`; + report += `- Przewyższenia: +${Math.round(totalElevationGain)}m / Max Wysokość: ${Math.round(maxElevation === -Infinity ? 0 : maxElevation)}m\n`; if (avgHr > 0) { report += `- Tętno: Średnie ${avgHr} bpm / Max ${maxHr} bpm\n`;