Skip to content

Commit 5da82f3

Browse files
committed
Replaces landing page with minimal welcome screen
Streamlines initial user experience by removing feature cards, detailed CTAs, and icon imports in favor of a lightweight welcome layout. Adds brand logo, title, subtitle, an Enter button for dashboard access, and a GitHub link as the primary calls to action.
1 parent a7e3acd commit 5da82f3

File tree

6 files changed

+102
-130
lines changed

6 files changed

+102
-130
lines changed

src/lib/components/panels/GuardianPanel.svelte

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import { onMount, tick } from 'svelte';
33
import { Terminal, ChevronRight } from 'lucide-svelte';
44
import { guardianHelpers } from '$lib/stores/guardian';
5-
import { aiAnalysisHelpers } from '$lib/stores/ai-analysis';
65
import { loadThemePreset, themePresets } from '$lib/stores/theme';
76
import { uiHelpers } from '$lib/stores/ui';
87
import { fetchOpenRouterModels } from '$lib/utils/ai-analysis';

src/lib/components/panels/Viewport.svelte

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
}
6767
6868
onMount(() => {
69-
// Reflect guardian API key validity for cloud capability
69+
// Reflect guardian API key validity for cloud capability
7070
guardianStore.subscribe((g) => {
7171
apiKeyValid = !!g?.apiKeyValid && !!g?.apiKey;
7272
});
@@ -187,7 +187,10 @@
187187
} catch (e) {
188188
const msg = String((e as Error)?.message || e);
189189
if (/429|Rate limit exceeded/i.test(msg)) {
190-
toast.info('Ruixen: Daily free limit reached', 'Try again tomorrow or add credits to OpenRouter');
190+
toast.info(
191+
'Ruixen: Daily free limit reached',
192+
'Try again tomorrow or add credits to OpenRouter'
193+
);
191194
} else {
192195
toast.error('Insight failed', (e as Error).message);
193196
}
@@ -216,7 +219,10 @@
216219
} catch (e) {
217220
const msg = String((e as Error)?.message || e);
218221
if (/429|Rate limit exceeded/i.test(msg)) {
219-
toast.info('Ruixen: Daily free limit reached', 'Try again tomorrow or add credits to OpenRouter');
222+
toast.info(
223+
'Ruixen: Daily free limit reached',
224+
'Try again tomorrow or add credits to OpenRouter'
225+
);
220226
} else {
221227
toast.error('Weekly analysis failed', (e as Error).message);
222228
}
@@ -563,11 +569,15 @@
563569
<div class="flex gap-2 mb-3">
564570
<Button
565571
variant="secondary"
566-
disabled={!selectedPet?.journalEntries?.length || runningInsight || isArchived(selectedPet)}
572+
disabled={!selectedPet?.journalEntries?.length ||
573+
runningInsight ||
574+
isArchived(selectedPet)}
567575
onclick={runLastEntryInsight}
568576
>
569577
{#if runningInsight}
570-
<div class="animate-spin w-4 h-4 border-2 border-current border-t-transparent rounded-full"></div>
578+
<div
579+
class="animate-spin w-4 h-4 border-2 border-current border-t-transparent rounded-full"
580+
></div>
571581
<span>Running…</span>
572582
{:else}
573583
<span>Analyze latest entry</span>
@@ -579,7 +589,9 @@
579589
onclick={runWeeklyCloud}
580590
>
581591
{#if runningWeekly}
582-
<div class="animate-spin w-4 h-4 border-2 border-current border-t-transparent rounded-full"></div>
592+
<div
593+
class="animate-spin w-4 h-4 border-2 border-current border-t-transparent rounded-full"
594+
></div>
583595
<span>Weekly summary…</span>
584596
{:else}
585597
<span>Run 1‑week analysis</span>

src/lib/stores/ruixen.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,7 @@ class Ruixen {
188188
const next = this.queue[0];
189189
if (!this.analyzer) {
190190
// Fallback for entire queue
191-
next.resolve(
192-
this.offlineHeuristic(next.pet, this.toJournalEntry(next.entry, next.pet))
193-
);
191+
next.resolve(this.offlineHeuristic(next.pet, this.toJournalEntry(next.entry, next.pet)));
194192
this.queue.shift();
195193
this.queueSize.set(this.queue.length);
196194
continue;
@@ -219,9 +217,7 @@ class Ruixen {
219217
saveDailyCount({ date: today, count: DAILY_FREE_LIMIT });
220218
this.dailyUsage.set(DAILY_FREE_LIMIT);
221219
}
222-
next.resolve(
223-
this.offlineHeuristic(next.pet, this.toJournalEntry(next.entry, next.pet))
224-
);
220+
next.resolve(this.offlineHeuristic(next.pet, this.toJournalEntry(next.entry, next.pet)));
225221
} finally {
226222
this.queue.shift();
227223
this.queueSize.set(this.queue.length);

src/lib/utils/ai-analysis.ts

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class AIAnalyzer {
2323

2424
async analyzeJournalEntry(pet: PetPanelData, entry: JournalEntry): Promise<AnalysisResult> {
2525
const prompt = this.buildAnalysisPrompt(pet, entry);
26-
const referer = typeof window !== 'undefined' ? window.location.origin : undefined;
26+
const referer = typeof window !== 'undefined' ? window.location.origin : undefined;
2727

2828
if (typeof window !== 'undefined' && (import.meta as any)?.env?.DEV) {
2929
console.debug('[Ruixen] Prompt (journal)', {
@@ -34,14 +34,14 @@ export class AIAnalyzer {
3434
}
3535

3636
try {
37-
const response = await fetch(this.baseUrl, {
37+
const response = await fetch(this.baseUrl, {
3838
method: 'POST',
3939
headers: {
4040
'Content-Type': 'application/json',
4141
},
4242
body: JSON.stringify({
43-
apiKey: this.apiKey,
44-
model: this.model,
43+
apiKey: this.apiKey,
44+
model: this.model,
4545
messages: [
4646
{
4747
role: 'system',
@@ -79,11 +79,12 @@ export class AIAnalyzer {
7979
}
8080

8181
async analyzeWeeklySummary(pet: PetPanelData): Promise<string> {
82-
const sevenDays = (pet.journalEntries || [])
83-
.slice()
84-
.filter((e) => Date.now() - new Date(e.date as any).getTime() <= 7 * 24 * 60 * 60 * 1000)
85-
.map((e) => `- ${new Date(e.date as any).toLocaleDateString()}: ${e.content}`)
86-
.join('\n') || 'No entries in last 7 days.';
82+
const sevenDays =
83+
(pet.journalEntries || [])
84+
.slice()
85+
.filter((e) => Date.now() - new Date(e.date as any).getTime() <= 7 * 24 * 60 * 60 * 1000)
86+
.map((e) => `- ${new Date(e.date as any).toLocaleDateString()}: ${e.content}`)
87+
.join('\n') || 'No entries in last 7 days.';
8788

8889
const prompt = `Summarize the last 7 days for ${pet.name} in 4-6 concise bullet points focused on mood, activity, appetite, and any warning signs.
8990
@@ -96,20 +97,20 @@ STRICT RULES:
9697
PET: ${pet.name} (${pet.breed || pet.species || 'pet'}, ${pet.age ?? 'unknown'}y)
9798
LAST 7 DAYS:\n${sevenDays}`;
9899

99-
const referer = typeof window !== 'undefined' ? window.location.origin : undefined;
100-
const response = await fetch(this.baseUrl, {
100+
const referer = typeof window !== 'undefined' ? window.location.origin : undefined;
101+
const response = await fetch(this.baseUrl, {
101102
method: 'POST',
102103
headers: {
103104
'Content-Type': 'application/json',
104105
},
105106
body: JSON.stringify({
106-
apiKey: this.apiKey,
107-
model: this.model,
107+
apiKey: this.apiKey,
108+
model: this.model,
108109
messages: [
109110
{
110111
role: 'system',
111112
content:
112-
'You are a veterinary AI assistant. Be concise and factual; never write fiction or narrative prose. Use only provided inputs.'
113+
'You are a veterinary AI assistant. Be concise and factual; never write fiction or narrative prose. Use only provided inputs.',
113114
},
114115
{ role: 'user', content: prompt },
115116
],
@@ -206,10 +207,7 @@ If the entry lacks enough information, use conservative defaults (moodTrend: "st
206207
let candidate = content.substring(braceIndex, lastBrace + 1);
207208
candidate = candidate.replace(/,\s*([}\]])/g, '$1');
208209
// Ensure summary is a closed string (fix common unterminated quote cases)
209-
candidate = candidate.replace(
210-
/("summary"\s*:\s*"[^"}]*)$/m,
211-
(match) => match + '"'
212-
);
210+
candidate = candidate.replace(/("summary"\s*:\s*"[^"}]*)$/m, (match) => match + '"');
213211
return JSON.parse(candidate);
214212
}
215213
}
@@ -223,7 +221,9 @@ If the entry lacks enough information, use conservative defaults (moodTrend: "st
223221
.split('\n')[0]
224222
.trim();
225223
return {
226-
summary: summary ? summary.slice(0, 180) + (summary.length > 180 ? '…' : '') : 'No summary available',
224+
summary: summary
225+
? summary.slice(0, 180) + (summary.length > 180 ? '…' : '')
226+
: 'No summary available',
227227
moodTrend: 'stable',
228228
activityLevel: 'normal',
229229
healthConcerns: [],
@@ -243,7 +243,8 @@ If the entry lacks enough information, use conservative defaults (moodTrend: "st
243243
): AnalysisResult {
244244
const safe = { ...res } as AnalysisResult;
245245
const s = String(safe.summary || '').trim();
246-
const looksLikeTitle = /:\s||/.test(s) && /^[A-Z][^.!?]{5,60}$/.test(s.split(/[.!?]/)[0] || '');
246+
const looksLikeTitle =
247+
/:\s||/.test(s) && /^[A-Z][^.!?]{5,60}$/.test(s.split(/[.!?]/)[0] || '');
247248
const hasYearOrPerson = /(18|19|20)\d{2}/.test(s) || /\b[A-Z][a-z]+\s+[A-Z][a-z]+\b/.test(s);
248249
const notAboutPet = /(diary|novel|chapter|story|keeper)/i.test(s);
249250
const hasSpecialTokens = /<\|.*?\|>|```/.test(s);
@@ -284,16 +285,16 @@ If the entry lacks enough information, use conservative defaults (moodTrend: "st
284285

285286
async testConnection(): Promise<boolean> {
286287
try {
287-
const referer = typeof window !== 'undefined' ? window.location.origin : undefined;
288-
const response = await fetch(this.baseUrl, {
288+
const referer = typeof window !== 'undefined' ? window.location.origin : undefined;
289+
const response = await fetch(this.baseUrl, {
289290
method: 'POST',
290291
headers: {
291292
'Content-Type': 'application/json',
292293
},
293294
body: JSON.stringify({
294-
apiKey: this.apiKey,
295-
model: this.model,
296-
messages: [{ role: 'user', content: 'test' }],
295+
apiKey: this.apiKey,
296+
model: this.model,
297+
messages: [{ role: 'user', content: 'test' }],
297298
max_tokens: 1,
298299
}),
299300
});

src/routes/+page.svelte

Lines changed: 47 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,56 @@
11
<script lang="ts">
2-
import { Heart, Activity, Calendar, Settings } from 'lucide-svelte';
3-
import Button from '$lib/components/ui/Button.svelte';
4-
import Card from '$lib/components/ui/Card.svelte';
2+
// Minimal welcome screen
3+
const go = () => (window.location.href = '/dashboard');
54
</script>
65

7-
<div class="min-h-screen" style="background: var(--petalytics-bg);">
8-
<div class="container mx-auto px-4 py-12">
9-
<!-- Header -->
10-
<div class="text-center mb-12">
11-
<div class="flex items-center justify-center mb-6">
12-
<Heart class="w-12 h-12 mr-3" style="color: var(--petalytics-love);" />
13-
<h1 class="text-5xl font-bold" style="color: var(--petalytics-text);">Petalytics</h1>
14-
</div>
15-
<p class="text-xl max-w-2xl mx-auto" style="color: var(--petalytics-subtle);">
16-
Your intelligent pet journal powered by AI. Track, analyze, and understand your furry
17-
friend's well-being.
18-
</p>
19-
</div>
6+
<div
7+
class="min-h-screen flex items-center justify-center px-4"
8+
style="background: var(--petalytics-bg);"
9+
>
10+
<main class="w-full max-w-xl text-center">
11+
<img
12+
src="/favicon.svg"
13+
alt="Petalytic logo"
14+
width="72"
15+
height="72"
16+
class="mx-auto mb-4"
17+
decoding="async"
18+
loading="eager"
19+
/>
20+
<h1
21+
class="mb-4 font-extrabold tracking-tight"
22+
style="color: var(--petalytics-text); font-size: clamp(2.25rem, 6vw, 4rem);"
23+
>
24+
Petalytic
25+
</h1>
26+
<p
27+
class="mx-auto mb-8"
28+
style="color: var(--petalytics-subtle); font-size: clamp(1rem, 2.5vw, 1.125rem);"
29+
>
30+
A free, open pet journaling and insights app. No upsell—just a friendly welcome.
31+
</p>
2032

21-
<!-- Features Grid -->
22-
<div class="grid md:grid-cols-3 gap-8 mb-12">
23-
<Card
24-
title="Smart Journaling"
25-
description="AI-powered insights from your pet's daily activities"
33+
<div class="flex items-center justify-center gap-3">
34+
<button
35+
class="px-6 py-3 rounded-md font-medium"
36+
style="background: var(--petalytics-accent); color: var(--petalytics-base);"
37+
onclick={go}
2638
>
27-
<Activity class="w-8 h-8 mb-4" style="color: var(--petalytics-pine);" />
28-
<p style="color: var(--petalytics-subtle);">
29-
Record your pet's behavior, health, and activities. Get intelligent recommendations based
30-
on patterns.
31-
</p>
32-
</Card>
33-
34-
<Card title="Health Tracking" description="Monitor your pet's wellness over time">
35-
<Heart class="w-8 h-8 mb-4" style="color: var(--petalytics-love);" />
36-
<p style="color: var(--petalytics-subtle);">
37-
Track weight, mood, appetite, and other vital signs to maintain optimal health.
38-
</p>
39-
</Card>
40-
41-
<Card title="Smart Reminders" description="Never miss important pet care tasks">
42-
<Calendar class="w-8 h-8 mb-4" style="color: var(--petalytics-foam);" />
43-
<p style="color: var(--petalytics-subtle);">
44-
Set up automated reminders for feeding, medication, vet visits, and grooming.
45-
</p>
46-
</Card>
47-
</div>
48-
49-
<!-- CTA Section -->
50-
<div class="text-center">
51-
<Card class="max-w-md mx-auto" padding="lg">
52-
<h2 class="text-2xl font-bold mb-4" style="color: var(--petalytics-text);">
53-
Ready to get started?
54-
</h2>
55-
<p class="mb-6" style="color: var(--petalytics-subtle);">
56-
Create your first pet profile and begin your intelligent journaling journey.
57-
</p>
58-
<div class="space-y-3">
59-
<Button
60-
variant="primary"
61-
size="lg"
62-
class="w-full"
63-
onclick={() => (window.location.href = '/dashboard')}
64-
>
65-
Open Dashboard
66-
</Button>
67-
<Button variant="secondary" size="lg" class="w-full">Add Your First Pet</Button>
68-
<Button variant="secondary" size="lg" class="w-full">
69-
<Settings class="w-4 h-4 mr-2" />
70-
Configure Settings
71-
</Button>
72-
</div>
73-
</Card>
39+
ENTER
40+
</button>
41+
<a
42+
class="px-5 py-3 rounded-md font-medium border"
43+
style="border-color: var(--petalytics-border); color: var(--petalytics-text);"
44+
href="https://github.com/gitcoder89431/petalytics"
45+
target="_blank"
46+
rel="noreferrer noopener"
47+
>
48+
GitHub ↗
49+
</a>
7450
</div>
7551

76-
<!-- Status Information -->
77-
<div class="mt-12 text-center">
78-
<div
79-
class="inline-flex items-center px-4 py-2 rounded-full text-sm font-medium"
80-
style="background: var(--petalytics-highlight-med); color: var(--petalytics-text);"
81-
>
82-
<div
83-
class="w-2 h-2 rounded-full mr-2 animate-pulse"
84-
style="background: var(--petalytics-foam);"
85-
></div>
86-
SvelteKit + TypeScript + Tailwind CSS - Theme System Implemented
87-
</div>
52+
<div class="mt-10 text-xs" style="color: var(--petalytics-subtle);">
53+
Welcome screen · Petalytic
8854
</div>
89-
</div>
55+
</main>
9056
</div>

src/routes/api/ai/analyze/+server.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,7 @@ import type { RequestHandler } from './$types';
55
export const POST: RequestHandler = async ({ request, fetch }) => {
66
try {
77
const body = await request.json();
8-
const {
9-
apiKey,
10-
model,
11-
messages,
12-
max_tokens,
13-
temperature,
14-
top_p,
15-
stop,
16-
} = body || {};
8+
const { apiKey, model, messages, max_tokens, temperature, top_p, stop } = body || {};
179

1810
if (!apiKey || typeof apiKey !== 'string') {
1911
return json({ error: 'Missing apiKey' }, { status: 400 });
@@ -40,7 +32,10 @@ export const POST: RequestHandler = async ({ request, fetch }) => {
4032

4133
const text = await resp.text();
4234
if (!resp.ok) {
43-
return json({ error: 'Upstream error', status: resp.status, body: text }, { status: resp.status });
35+
return json(
36+
{ error: 'Upstream error', status: resp.status, body: text },
37+
{ status: resp.status }
38+
);
4439
}
4540
// Pass through upstream JSON
4641
try {
@@ -51,6 +46,9 @@ export const POST: RequestHandler = async ({ request, fetch }) => {
5146
}
5247
} catch (error: any) {
5348
console.error('AI proxy error:', error);
54-
return json({ error: 'Proxy failure', detail: String(error?.message || error) }, { status: 500 });
49+
return json(
50+
{ error: 'Proxy failure', detail: String(error?.message || error) },
51+
{ status: 500 }
52+
);
5553
}
5654
};

0 commit comments

Comments
 (0)