diff --git a/src/App.jsx b/src/App.jsx index 997cb51..8ccf14b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -15,6 +15,7 @@ import BrewCard from "./components/BrewCard"; import BottomNav from "./components/BottomNav"; import IosPromptModal from "./components/IosPromptModal"; import UpdatePrompt from "./components/UpdatePrompt"; +import SyncIndicator from "./components/SyncIndicator"; // Import constants @@ -196,13 +197,7 @@ export default function CoffeeLogbook() {
{pageSubtitles[view] || "Coffee Logbook"}

{pageTitles[view] || "Brew Journal"}

- {showSyncedStatus && ( -
- - {isOnline ? (syncing ? "Syncing..." : "Synced") : "Offline"} -
- )} + {/* Content */} diff --git a/src/components/SyncIndicator.jsx b/src/components/SyncIndicator.jsx new file mode 100644 index 0000000..e89f104 --- /dev/null +++ b/src/components/SyncIndicator.jsx @@ -0,0 +1,69 @@ +/** + * SyncIndicator — a minimal coffee-cup icon that communicates sync results. + * + * Success: outlined cup → steam rises → checkmark appears → fades out (~2.4s) + * Offline: outlined cup + subtle "!" warning, stays visible. + */ +export default function SyncIndicator({ isOnline, syncing, showSyncedStatus }) { + const showSuccess = isOnline && showSyncedStatus && !syncing; + const showOffline = !isOnline; + + if (!showSuccess && !showOffline) return null; + + return ( +
+ + {/* ── Cup body ── */} + + + + + + {/* ── Steam lines (success only) ── */} + {showSuccess && ( + + + + + + )} + + {/* ── Checkmark (success only) ── */} + {showSuccess && ( + + )} + + {/* ── Warning indicator (offline only) ── */} + {showOffline && ( + + + + + )} + +
+ ); +} diff --git a/src/index.css b/src/index.css index f92fed4..950584b 100644 --- a/src/index.css +++ b/src/index.css @@ -138,4 +138,40 @@ width: 4px; border-radius: 0 4px 4px 0; } + + /* ── Sync cup indicator ── */ + .sync-cup-success { + animation: sync-cup-fade 2.4s ease-in-out forwards; + } + + @keyframes sync-cup-fade { + 0% { opacity: 0; transform: scale(0.88); } + 8% { opacity: 1; transform: scale(1); } + 68% { opacity: 1; } + 100% { opacity: 0; } + } + + .sync-steam { + opacity: 0; + } + + .sync-steam-1 { animation: sync-steam-rise 1.4s ease-out 0.1s forwards; } + .sync-steam-2 { animation: sync-steam-rise 1.4s ease-out 0.3s forwards; } + .sync-steam-3 { animation: sync-steam-rise 1.4s ease-out 0.5s forwards; } + + @keyframes sync-steam-rise { + 0% { opacity: 0; transform: translateY(0); } + 25% { opacity: 0.5; } + 100% { opacity: 0; transform: translateY(-4px); } + } + + .sync-check { + stroke-dasharray: 14; + stroke-dashoffset: 14; + animation: sync-check-draw 0.45s ease-out 0.65s forwards; + } + + @keyframes sync-check-draw { + to { stroke-dashoffset: 0; } + } } diff --git a/src/pages/ProfilePage.jsx b/src/pages/ProfilePage.jsx index 744cd8f..f0cc829 100644 --- a/src/pages/ProfilePage.jsx +++ b/src/pages/ProfilePage.jsx @@ -23,12 +23,10 @@ export default function ProfilePage({ user, isOnline, syncing, showSyncedStatus
{user?.username}
{user?.email}
- {showSyncedStatus && ( -
- - {isOnline ? (syncing ? "Syncing…" : "All data synced") : "Offline — saved locally"} -
- )} +
+ + {isOnline ? (syncing ? "Syncing…" : "All data synced") : "Offline — saved locally"} +
{/* Account section */} diff --git a/vite.config.js b/vite.config.js index 7a3577d..cedc464 100644 --- a/vite.config.js +++ b/vite.config.js @@ -25,12 +25,7 @@ function getGitVersion() { return `v0.0.0-${raw}`; } - // If it's a tag + commits + hash (e.g., v0.1.1-5-g4a9f6b6) - // Format it cleanly to v0.1.1-4a9f6b6 - const match = raw.match(/^(.*)-\d+-g([0-9a-f]+)$/); - if (match) { - return `${match[1]}-${match[2]}`; - } + // Otherwise, return raw (e.g., v0.1.1-5-g4a9f6b6 or v1.2.0) // Exact tag (e.g., v1.2.0) return raw;