Database Viewer Update

This commit is contained in:
2026-01-25 00:16:48 +00:00
parent d37d925150
commit bae861c71f
15 changed files with 1726 additions and 846 deletions

View File

@@ -0,0 +1,101 @@
<script lang="ts">
import Icon from "@iconify/svelte";
let {
viewMode = $bindable(),
searchQuery = $bindable(),
selectedCategory = $bindable(),
categories,
onSearchInput,
}: {
viewMode: "company" | "user";
searchQuery: string;
selectedCategory: string;
categories: string[];
onSearchInput: () => void;
} = $props();
</script>
<div
class="bg-black/80 backdrop-blur-[30px] border border-white/20 rounded-[32px] p-10 mb-10 shadow-[0_32px_64px_rgba(0,0,0,0.4)]"
>
<!-- Header content -->
<div class="mb-8 px-3 text-center">
<h1 class="text-white text-[42px] font-black m-0 tracking-[-2px]">
Sustainability Database
</h1>
<p class="text-white/70 text-base mt-2 font-medium">
{#if viewMode === "company"}
Search within verified company reports and impact assessments
{:else}
Browse user-submitted greenwashing reports
{/if}
</p>
</div>
<!-- View Mode Toggle Switch -->
<div class="flex items-center justify-center gap-4 my-6">
<span
class="flex items-center gap-1.5 text-sm font-semibold transition-all duration-300 {viewMode ===
'company'
? 'text-emerald-400'
: 'text-white/40'}"
>
<Icon icon="ri:building-2-line" width="16" />
Company
</span>
<button
class="relative w-14 h-7 bg-white/15 border-none rounded-full cursor-pointer transition-all duration-300 p-0 hover:bg-white/20"
onclick={() =>
(viewMode = viewMode === "company" ? "user" : "company")}
aria-label="Toggle between company and user reports"
>
<span
class="absolute top-1/2 -translate-y-1/2 w-[22px] h-[22px] bg-emerald-600 rounded-full transition-all duration-300 shadow-[0_2px_8px_rgba(34,197,94,0.4)] {viewMode ===
'user'
? 'left-[calc(100%-25px)]'
: 'left-[3px]'}"
></span>
</button>
<span
class="flex items-center gap-1.5 text-sm font-semibold transition-all duration-300 {viewMode ===
'user'
? 'text-emerald-400'
: 'text-white/40'}"
>
<Icon icon="ri:user-voice-line" width="16" />
User Reports
</span>
</div>
{#if viewMode === "company"}
<div class="relative max-w-[600px] mx-auto mb-8">
<div
class="absolute left-5 top-1/2 -translate-y-1/2 text-white/60 flex items-center pointer-events-none"
>
<Icon icon="ri:search-line" width="20" />
</div>
<input
type="text"
class="w-full bg-white/5 border border-white/10 rounded-full py-4 pl-[52px] pr-5 text-white text-base font-medium outline-none transition-all duration-200 focus:bg-white/10 focus:border-emerald-400 placeholder:text-white/40"
placeholder="Search for companies, topics (e.g., 'emissions')..."
bind:value={searchQuery}
oninput={onSearchInput}
/>
</div>
<div class="flex justify-center flex-wrap gap-3 mb-5">
{#each categories as category}
<button
class="px-6 py-2.5 rounded-full text-sm font-semibold cursor-pointer transition-all duration-200 border {selectedCategory ===
category
? 'bg-emerald-500 border-emerald-500 text-emerald-950 shadow-[0_4px_15px_rgba(34,197,94,0.3)]'
: 'bg-white/5 border-white/10 text-white/70 hover:bg-white/10 hover:text-white hover:-translate-y-0.5'}"
onclick={() => (selectedCategory = category)}
>
{category}
</button>
{/each}
</div>
{/if}
</div>

View File

@@ -0,0 +1,86 @@
<script lang="ts">
import Icon from "@iconify/svelte";
import { fade, scale } from "svelte/transition";
interface Report {
company_name: string;
year: string | number;
sector: string;
greenwashing_score: number | string;
filename: string;
title?: string;
snippet?: string;
}
let { report, onclose }: { report: Report; onclose: () => void } = $props();
</script>
<div
class="fixed inset-0 bg-black/80 backdrop-blur-md z-1000 flex justify-center items-center p-5 outline-none"
onclick={onclose}
onkeydown={(e) => e.key === "Escape" && onclose()}
transition:fade={{ duration: 200 }}
role="button"
tabindex="0"
aria-label="Close modal"
>
<div
class="bg-slate-900 border border-slate-700 rounded-[24px] w-full max-w-[1000px] h-[90vh] flex flex-col shadow-[0_24px_64px_rgba(0,0,0,0.5)] overflow-hidden outline-none"
onclick={(e) => e.stopPropagation()}
onkeydown={(e) => e.stopPropagation()}
transition:scale={{ duration: 300, start: 0.95 }}
role="document"
tabindex="0"
>
<div
class="px-6 py-5 bg-slate-800 border-b border-slate-700 flex justify-between items-center shrink-0"
>
<div class="modal-title-group">
<h2 class="m-0 text-white text-xl font-bold">
{report.company_name}
</h2>
<span class="block mt-1 text-slate-400 text-sm"
>{report.title || report.filename}</span
>
</div>
<button
class="bg-transparent border-none text-slate-400 cursor-pointer p-1 flex transition-colors duration-200 hover:text-white"
onclick={onclose}
>
<Icon icon="ri:close-line" width="24" />
</button>
</div>
<div class="flex-1 relative overflow-hidden bg-black flex flex-col">
{#if report.filename
.toLowerCase()
.endsWith(".pdf") || report.filename
.toLowerCase()
.endsWith(".txt")}
<iframe
src="http://localhost:5000/api/reports/view/{report.filename}"
class="w-full h-full border-none"
title="Report Viewer"
></iframe>
{:else}
<div
class="flex-1 flex flex-col items-center justify-center text-slate-400 gap-5"
>
<Icon
icon="ri:file-warning-line"
width="64"
class="text-slate-500"
/>
<p>Preview not available for this file type.</p>
<a
href="http://localhost:5000/api/reports/view/{report.filename}"
download
class="bg-emerald-400 text-slate-900 px-6 py-3 rounded-full no-underline font-bold transition-transform hover:scale-105"
>
Download File
</a>
</div>
{/if}
</div>
</div>
</div>

View File

@@ -0,0 +1,86 @@
<script lang="ts">
import Icon from "@iconify/svelte";
interface Incident {
_id: string;
product_name: string;
detected_brand: string;
user_description?: string;
created_at: string;
analysis: {
verdict: string;
confidence: string;
severity: string;
reasoning: string;
is_greenwashing: boolean;
};
}
let { incident, onclick }: { incident: Incident; onclick: () => void } =
$props();
</script>
<button
class="flex items-center gap-6 p-6 bg-black/80 backdrop-blur-[30px] border border-red-500/30 rounded-[24px] transition-all duration-300 shadow-[0_16px_48px_rgba(0,0,0,0.5)] w-full text-left cursor-pointer hover:bg-black/95 hover:border-red-500/60 hover:-translate-y-1 hover:scale-[1.01] hover:shadow-[0_24px_64px_rgba(0,0,0,0.6)] outline-none"
{onclick}
>
<div
class="w-[52px] h-[52px] rounded-xe flex items-center justify-center shrink-0 rounded-[14px]
{incident.analysis?.severity === 'high'
? 'bg-red-500/20 text-red-500'
: 'bg-amber-500/20 text-amber-500'}"
>
<Icon icon="ri:alert-fill" width="28" />
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-3 mb-1.5">
<h3 class="text-white text-[18px] font-bold m-0 italic">
{incident.product_name}
</h3>
{#if incident.detected_brand && incident.detected_brand !== "Unknown"}
<span
class="bg-white/10 text-white/70 px-2.5 py-0.5 rounded-full text-[12px] font-semibold"
>{incident.detected_brand}</span
>
{/if}
</div>
<p class="text-white/70 text-sm m-0 mb-2.5 leading-tight italic">
{incident.analysis?.verdict || "Greenwashing detected"}
</p>
<div class="flex flex-wrap gap-2">
<span
class="flex items-center gap-1 px-2.5 py-1 rounded-full text-[11px] font-semibold capitalize
{incident.analysis?.severity === 'high'
? 'bg-red-500/20 text-red-400'
: incident.analysis?.severity === 'medium'
? 'bg-amber-500/20 text-amber-400'
: 'bg-emerald-500/20 text-emerald-400'}"
>
<Icon icon="ri:error-warning-fill" width="14" />
{incident.analysis?.severity || "unknown"} severity
</span>
<span
class="flex items-center gap-1 px-2.5 py-1 rounded-full text-[11px] font-semibold bg-indigo-500/20 text-indigo-300"
>
<Icon icon="ri:shield-check-fill" width="14" />
{incident.analysis?.confidence || "unknown"} confidence
</span>
<span
class="flex items-center gap-1 px-2.5 py-1 rounded-full text-[11px] font-semibold bg-white/10 text-white/60"
>
<Icon icon="ri:calendar-line" width="14" />
{new Date(incident.created_at).toLocaleDateString()}
</span>
</div>
</div>
<div
class="flex flex-col items-center gap-1 text-[11px] font-bold uppercase text-red-500"
>
<Icon icon="ri:spam-2-fill" width="24" class="text-red-500" />
<span>Confirmed</span>
</div>
</button>

View File

@@ -0,0 +1,233 @@
<script lang="ts">
import Icon from "@iconify/svelte";
import { fade, scale } from "svelte/transition";
interface Incident {
_id: string;
product_name: string;
detected_brand: string;
user_description?: string;
created_at: string;
analysis: {
verdict: string;
confidence: string;
severity: string;
reasoning: string;
is_greenwashing: boolean;
key_claims?: string[];
red_flags?: string[];
recommendations?: string;
};
}
let { incident, onclose }: { incident: Incident; onclose: () => void } =
$props();
</script>
<div
class="fixed inset-0 bg-black/60 backdrop-blur-md z-1000 flex justify-center items-center p-5 outline-none"
onclick={onclose}
onkeydown={(e) => e.key === "Escape" && onclose()}
transition:fade={{ duration: 200 }}
role="button"
tabindex="0"
aria-label="Close modal"
>
<div
class="max-w-[700px] max-h-[85vh] w-full overflow-y-auto bg-black/85 backdrop-blur-[40px] border border-white/20 rounded-[32px] flex flex-col shadow-[0_32px_128px_rgba(0,0,0,0.7)] scrollbar-hide outline-none"
onclick={(e) => e.stopPropagation()}
onkeydown={(e) => e.stopPropagation()}
transition:scale={{ duration: 300, start: 0.95 }}
role="document"
tabindex="0"
>
<div
class="px-10 py-[30px] bg-white/3 border-b border-red-500/20 flex justify-between items-center shrink-0"
>
<div class="flex flex-col">
<div class="flex items-center gap-3">
<Icon
icon="ri:alert-fill"
width="28"
class="text-red-500"
/>
<h2 class="m-0 text-white text-[28px] font-extrabold">
{incident.product_name}
</h2>
</div>
{#if incident.detected_brand && incident.detected_brand !== "Unknown"}
<span class="mt-1 text-white/50 text-sm font-medium"
>Brand: {incident.detected_brand}</span
>
{/if}
</div>
<button
class="bg-white/5 border border-white/10 text-white w-10 h-10 rounded-xl flex items-center justify-center cursor-pointer transition-all duration-200 hover:bg-white/10 hover:rotate-90"
onclick={onclose}
>
<Icon icon="ri:close-line" width="24" />
</button>
</div>
<div class="p-10 flex flex-col gap-[30px]">
<!-- Status Badges -->
<div class="flex flex-wrap gap-3">
<span
class="flex items-center gap-2 px-5 py-3 rounded-[14px] text-[11px] font-extrabold tracking-wider
{incident.analysis?.severity === 'high'
? 'bg-red-500/20 text-red-400 border border-red-500/30'
: incident.analysis?.severity === 'medium'
? 'bg-amber-500/20 text-amber-400 border border-amber-500/30'
: 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30'} uppercase"
>
<Icon icon="ri:error-warning-fill" width="18" />
{incident.analysis?.severity || "UNKNOWN"} SEVERITY
</span>
<span
class="flex items-center gap-2 px-5 py-3 rounded-[14px] text-[11px] font-extrabold tracking-wider bg-indigo-500/20 text-indigo-300 border border-indigo-500/30 uppercase"
>
<Icon icon="ri:shield-check-fill" width="18" />
{incident.analysis?.confidence || "UNKNOWN"} CONFIDENCE
</span>
<span
class="flex items-center gap-2 px-5 py-3 rounded-[14px] text-[11px] font-extrabold tracking-wider bg-white/10 text-white/70 border border-white/10 uppercase"
>
<Icon icon="ri:calendar-check-fill" width="18" />
{new Date(incident.created_at).toLocaleDateString()}
</span>
</div>
<!-- Verdict -->
<div class="bg-white/4 border border-white/6 rounded-[20px] p-6">
<h3
class="flex items-center gap-[10px] text-white text-base font-bold mb-4"
>
<Icon icon="ri:scales-3-fill" width="20" />
Verdict
</h3>
<p
class="text-amber-400 text-[18px] font-semibold m-0 leading-normal"
>
{incident.analysis?.verdict || "Greenwashing detected"}
</p>
</div>
<!-- Detailed Analysis -->
<div class="bg-white/4 border border-white/6 rounded-[20px] p-6">
<h3
class="flex items-center gap-[10px] text-white text-base font-bold mb-4"
>
<Icon icon="ri:file-text-fill" width="20" />
Detailed Analysis
</h3>
<p class="text-white/85 text-[15px] leading-[1.7] m-0">
{incident.analysis?.reasoning ||
"No detailed analysis available."}
</p>
</div>
<!-- Red Flags -->
{#if incident.analysis?.red_flags && incident.analysis.red_flags.length > 0}
<div
class="bg-white/4 border border-white/[0.06] rounded-[20px] p-6"
>
<h3
class="flex items-center gap-[10px] text-red-400 text-base font-bold mb-4"
>
<Icon icon="ri:flag-fill" width="20" />
Red Flags Identified
</h3>
<ul class="list-none p-0 m-0 flex flex-col gap-3">
{#each incident.analysis.red_flags as flag}
<li
class="flex items-start gap-3 text-red-300/80 text-sm leading-[1.6]"
>
<Icon
icon="ri:error-warning-line"
width="16"
class="mt-0.5 shrink-0"
/>
{flag}
</li>
{/each}
</ul>
</div>
{/if}
<!-- Key Claims -->
{#if incident.analysis?.key_claims && incident.analysis.key_claims.length > 0}
<div
class="bg-white/4 border border-white/[0.06] rounded-[20px] p-6"
>
<h3
class="flex items-center gap-[10px] text-white text-base font-bold mb-4"
>
<Icon icon="ri:chat-quote-fill" width="20" />
Environmental Claims Made
</h3>
<ul class="list-none p-0 m-0 flex flex-col gap-3">
{#each incident.analysis.key_claims as claim}
<li
class="flex items-start gap-3 text-white/70 text-sm italic leading-[1.6]"
>
<Icon
icon="ri:double-quotes-l"
width="16"
class="mt-0.5 shrink-0"
/>
{claim}
</li>
{/each}
</ul>
</div>
{/if}
<!-- Recommendations -->
{#if incident.analysis?.recommendations}
<div
class="bg-emerald-500/8 border border-emerald-500/20 rounded-[20px] p-6"
>
<h3
class="flex items-center gap-[10px] text-emerald-400 text-base font-bold mb-4"
>
<Icon icon="ri:lightbulb-fill" width="20" />
Consumer Recommendations
</h3>
<p class="text-emerald-300 text-[15px] leading-[1.6] m-0">
{incident.analysis.recommendations}
</p>
</div>
{/if}
<!-- User's Original Report -->
{#if incident.user_description}
<div
class="bg-indigo-500/8 border border-indigo-500/20 rounded-[20px] p-6"
>
<h3
class="flex items-center gap-[10px] text-indigo-400 text-base font-bold mb-4"
>
<Icon icon="ri:user-voice-fill" width="20" />
Original User Report
</h3>
<p
class="text-indigo-200 text-[15px] italic leading-[1.6] m-0"
>
"{incident.user_description}"
</p>
</div>
{/if}
</div>
</div>
</div>
<style>
/* Custom utility to hide scrollbar if tailwind plugin not present */
.scrollbar-hide {
scrollbar-width: none;
-ms-overflow-style: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
</style>

View File

@@ -0,0 +1,53 @@
<script lang="ts">
import Icon from "@iconify/svelte";
let {
currentPage = $bindable(),
totalPages,
goToPage,
}: {
currentPage: number;
totalPages: number;
goToPage: (p: number) => void;
} = $props();
</script>
{#if totalPages > 1}
<div class="flex items-center justify-center gap-4 mt-6">
<button
class="w-12 h-12 flex items-center justify-center rounded-xl bg-white/5 border border-white/10 text-white cursor-pointer transition-all duration-200 hover:enabled:bg-white/15 hover:enabled:scale-105 disabled:opacity-30 disabled:cursor-not-allowed"
disabled={currentPage === 1}
onclick={() => goToPage(currentPage - 1)}
>
<Icon icon="ri:arrow-left-s-line" width="24" />
</button>
<div class="flex gap-2">
{#if totalPages <= 7}
{#each Array(totalPages) as _, i}
<button
class="px-[18px] h-12 flex items-center justify-center rounded-xl border font-bold text-sm cursor-pointer transition-all duration-200 hover:enabled:bg-white/15 hover:enabled:scale-105 disabled:opacity-30 disabled:cursor-not-allowed
{currentPage === i + 1
? 'bg-emerald-500 border-emerald-500 text-emerald-950'
: 'bg-white/5 border-white/10 text-white'}"
onclick={() => goToPage(i + 1)}
>
{i + 1}
</button>
{/each}
{:else}
<span class="text-white/50 text-sm font-semibold self-center">
Page {currentPage} of {totalPages}
</span>
{/if}
</div>
<button
class="w-12 h-12 flex items-center justify-center rounded-xl bg-white/5 border border-white/10 text-white cursor-pointer transition-all duration-200 hover:enabled:bg-white/15 hover:enabled:scale-105 disabled:opacity-30 disabled:cursor-not-allowed"
disabled={currentPage === totalPages}
onclick={() => goToPage(currentPage + 1)}
>
<Icon icon="ri:arrow-right-s-line" width="24" />
</button>
</div>
{/if}

View File

@@ -0,0 +1,120 @@
<script lang="ts">
import Icon from "@iconify/svelte";
interface Report {
company_name: string;
year: string | number;
sector: string;
greenwashing_score: number | string;
filename: string;
title?: string;
snippet?: string;
}
let {
report,
openReport,
searchQuery,
}: {
report: Report;
openReport: (r: Report) => void;
searchQuery: string;
} = $props();
function getFileDetails(filename: string) {
const ext = filename.split(".").pop()?.toLowerCase();
if (ext === "pdf")
return { type: "PDF Document", icon: "ri:file-pdf-2-line" };
if (ext === "xlsx" || ext === "csv")
return { type: "Data Spreadsheet", icon: "ri:file-excel-2-line" };
if (ext === "txt")
return { type: "Text Report", icon: "ri:file-text-line" };
return { type: "Impact Report", icon: "ri:file-info-line" };
}
function getScoreColor(score: string | number) {
const s = Number(score);
if (s >= 80) return "bg-emerald-500";
if (s >= 50) return "bg-amber-500";
return "bg-red-500";
}
const fileDetails = $derived(getFileDetails(report.filename));
</script>
<button
class="group flex items-center gap-6 bg-black/80 backdrop-blur-[30px] border border-white/20 rounded-3xl p-8 w-full text-left cursor-pointer transition-all duration-300 hover:bg-black/90 hover:border-emerald-500/60 hover:-translate-y-1 hover:scale-[1.01] hover:shadow-[0_24px_48px_rgba(0,0,0,0.5)] relative overflow-hidden outline-none"
onclick={() => openReport(report)}
>
<div
class="w-16 h-16 bg-emerald-500/10 rounded-[18px] flex items-center justify-center shrink-0 transition-all duration-300 group-hover:bg-emerald-500 group-hover:-rotate-6"
>
<Icon icon={fileDetails.icon} width="32" class="text-white" />
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-3 mb-2">
<h3 class="text-white text-xl font-extrabold m-0">
{report.company_name}
</h3>
<span
class="text-emerald-400 bg-emerald-500/10 px-2 py-0.5 rounded-md text-[12px] font-bold"
>{report.year}</span
>
</div>
{#if report.snippet}
<p
class="text-white/60 text-sm leading-relaxed mb-4 line-clamp-2 m-0"
>
{@html report.snippet.replace(
new RegExp(searchQuery || "", "gi"),
(match) =>
`<span class="text-emerald-400 bg-emerald-500/20 px-0.5 rounded font-semibold">${match}</span>`,
)}
</p>
{:else}
<p class="text-white/40 text-sm mb-4 m-0">
{report.sector} Sector • Impact Report
</p>
{/if}
<div class="flex flex-wrap gap-2">
<span
class="flex items-center gap-1.5 px-3 py-1.5 rounded-xl text-[11px] font-bold tracking-tight bg-white/5 text-white/60 max-w-[200px] truncate"
title={report.filename}
>
<Icon icon={fileDetails.icon} width="14" />
{fileDetails.type}
</span>
<span
class="flex items-center gap-1.5 px-3 py-1.5 rounded-xl text-[11px] font-bold tracking-tight bg-emerald-500/10 text-emerald-400"
>
<Icon
icon="ri:checkbox-circle-fill"
width="14"
class="text-emerald-500"
/>
Analyzed
</span>
</div>
</div>
{#if report.greenwashing_score}
<div class="text-center ml-5">
<div
class="w-[52px] h-[52px] rounded-xe flex items-center justify-center mb-1 rounded-[14px] {getScoreColor(
report.greenwashing_score,
)}"
>
<span class="text-emerald-950 text-[18px] font-black"
>{Math.round(Number(report.greenwashing_score))}</span
>
</div>
<span
class="text-white/40 text-[10px] font-extrabold uppercase tracking-widest"
>Trust Score</span
>
</div>
{/if}
</button>

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,9 @@
let description = $state("");
let image = $state<string | null>(null);
let submitted = $state(false);
let isLoading = $state(false);
let analysisResult = $state<any>(null);
let error = $state<string | null>(null);
$effect(() => {
const initialName = new URLSearchParams(window.location.search).get(
@@ -37,12 +40,75 @@
input.click();
}
function handleSubmit() {
if (!isValid) return;
submitted = true;
setTimeout(() => {
window.history.back();
}, 2000);
const progressSteps = [
{ id: 1, label: "Scanning image...", icon: "ri:camera-lens-line" },
{
id: 2,
label: "Detecting brand logos...",
icon: "ri:search-eye-line",
},
{ id: 3, label: "Searching database...", icon: "ri:database-2-line" },
{ id: 4, label: "AI analysis in progress...", icon: "ri:robot-2-line" },
{
id: 5,
label: "Generating verdict...",
icon: "ri:file-shield-2-line",
},
];
let currentStep = $state(0);
async function handleSubmit() {
if (!isValid || isLoading) return;
isLoading = true;
error = null;
currentStep = 1;
// Simulate progress steps
const stepInterval = setInterval(() => {
if (currentStep < progressSteps.length) {
currentStep++;
}
}, 1500);
try {
const response = await fetch(
"http://localhost:5000/api/incidents/submit",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
product_name: productName,
description: description,
image: image, // Base64 encoded
}),
},
);
clearInterval(stepInterval);
currentStep = progressSteps.length; // Complete all steps
const data = await response.json();
if (data.status === "success") {
// Brief pause to show completion
await new Promise((r) => setTimeout(r, 500));
analysisResult = data;
submitted = true;
} else {
error = data.message || "Failed to submit report";
}
} catch (e) {
clearInterval(stepInterval);
error = "Failed to connect to server. Please try again.";
console.error("Submit error:", e);
} finally {
isLoading = false;
currentStep = 0;
}
}
</script>
@@ -52,19 +118,79 @@
</div>
<div class="content-container">
{#if submitted}
{#if submitted && analysisResult}
<div class="glass-card success-card">
<div class="icon-circle success">
<iconify-icon
icon="ri:checkbox-circle-fill"
width="60"
style="color: #4ade80;"
></iconify-icon>
{#if analysisResult.is_greenwashing}
<div class="icon-circle warning">
<iconify-icon
icon="ri:alert-fill"
width="60"
style="color: #f59e0b;"
></iconify-icon>
</div>
<h2 class="success-title warning-text">
Greenwashing Detected!
</h2>
{:else}
<div class="icon-circle success">
<iconify-icon
icon="ri:checkbox-circle-fill"
width="60"
style="color: #4ade80;"
></iconify-icon>
</div>
<h2 class="success-title">Report Analyzed</h2>
{/if}
<div class="analysis-result">
<div class="result-item">
<span class="result-label">Verdict:</span>
<span class="result-value"
>{analysisResult.analysis?.verdict || "N/A"}</span
>
</div>
<div class="result-item">
<span class="result-label">Confidence:</span>
<span
class="result-value badge {analysisResult.analysis
?.confidence}"
>
{analysisResult.analysis?.confidence || "unknown"}
</span>
</div>
{#if analysisResult.is_greenwashing}
<div class="result-item">
<span class="result-label">Severity:</span>
<span
class="result-value badge severity-{analysisResult
.analysis?.severity}"
>
{analysisResult.analysis?.severity || "unknown"}
</span>
</div>
{/if}
<div class="result-item full-width">
<span class="result-label">Analysis:</span>
<p class="result-text">
{analysisResult.analysis?.reasoning ||
"No details available"}
</p>
</div>
{#if analysisResult.detected_brand && analysisResult.detected_brand !== "Unknown"}
<div class="result-item">
<span class="result-label">Detected Brand:</span>
<span class="result-value"
>{analysisResult.detected_brand}</span
>
</div>
{/if}
</div>
<h2 class="success-title">Report Submitted!</h2>
<p class="success-subtitle">
Thank you for keeping companies honest.
</p>
<button class="back-btn" onclick={() => window.history.back()}>
<iconify-icon icon="ri:arrow-left-line" width="20"
></iconify-icon>
Go Back
</button>
</div>
{:else}
<div class="header-section">
@@ -143,17 +269,80 @@
</button>
</div>
<button
class="submit-button"
class:disabled={!isValid}
disabled={!isValid}
onclick={handleSubmit}
type="button"
>
<iconify-icon icon="ri:alert-fill" width="20"
></iconify-icon>
Submit Report
</button>
{#if error}
<div class="error-message">
<iconify-icon
icon="ri:error-warning-line"
width="18"
></iconify-icon>
{error}
</div>
{/if}
{#if isLoading}
<div class="progress-container">
<div class="progress-header">
<iconify-icon
icon="ri:shield-check-line"
width="24"
class="pulse"
></iconify-icon>
<span>Analyzing Report</span>
</div>
<div class="progress-steps">
{#each progressSteps as step}
<div
class="progress-step"
class:active={currentStep >= step.id}
class:current={currentStep === step.id}
>
<div class="step-icon">
{#if currentStep > step.id}
<iconify-icon
icon="ri:check-line"
width="16"
></iconify-icon>
{:else if currentStep === step.id}
<iconify-icon
icon={step.icon}
width="16"
class="spin-slow"
></iconify-icon>
{:else}
<iconify-icon
icon={step.icon}
width="16"
></iconify-icon>
{/if}
</div>
<span class="step-label"
>{step.label}</span
>
</div>
{/each}
</div>
<div class="progress-bar">
<div
class="progress-fill"
style="width: {(currentStep /
progressSteps.length) *
100}%"
></div>
</div>
</div>
{:else}
<button
class="submit-button"
class:disabled={!isValid}
disabled={!isValid}
onclick={handleSubmit}
type="button"
>
<iconify-icon icon="ri:shield-flash-line" width="20"
></iconify-icon>
Analyze for Greenwashing
</button>
{/if}
</div>
</div>
{/if}
@@ -175,22 +364,22 @@
.content-container {
position: relative;
z-index: 10;
padding: 100px 24px 120px;
max-width: 600px;
padding: 80px 20px 40px;
max-width: 500px;
margin: 0 auto;
}
.header-section {
text-align: center;
margin-bottom: 32px;
margin-bottom: 20px;
}
.page-title {
color: white;
font-size: 36px;
font-size: 28px;
font-weight: 900;
margin: 0;
letter-spacing: -1px;
letter-spacing: -0.5px;
}
.subtitle {
@@ -212,19 +401,19 @@
}
.form-card {
padding: 36px;
padding: 24px;
}
.form-content {
display: flex;
flex-direction: column;
gap: 24px;
gap: 16px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 10px;
gap: 6px;
}
.label {
@@ -412,6 +601,239 @@
margin: 0;
}
.warning-text {
color: #f59e0b !important;
}
.icon-circle.warning {
background: rgba(245, 158, 11, 0.15);
}
.analysis-result {
display: flex;
flex-direction: column;
gap: 12px;
margin-top: 20px;
text-align: left;
width: 100%;
}
.result-item {
display: flex;
align-items: flex-start;
gap: 8px;
}
.result-item.full-width {
flex-direction: column;
}
.result-label {
color: rgba(255, 255, 255, 0.5);
font-size: 13px;
font-weight: 600;
min-width: 80px;
}
.result-value {
color: white;
font-size: 14px;
}
.result-text {
color: rgba(255, 255, 255, 0.8);
font-size: 14px;
line-height: 1.5;
margin: 4px 0 0 0;
}
.badge {
padding: 2px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
}
.badge.high {
background: #ef4444;
color: white;
}
.badge.medium {
background: #f59e0b;
color: #000;
}
.badge.low {
background: #22c55e;
color: #000;
}
.severity-high {
background: #ef4444 !important;
}
.severity-medium {
background: #f59e0b !important;
}
.severity-low {
background: #22c55e !important;
}
.back-btn {
margin-top: 24px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
color: white;
padding: 12px 24px;
border-radius: 50px;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.2s;
}
.back-btn:hover {
background: rgba(255, 255, 255, 0.2);
}
.error-message {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
background: rgba(239, 68, 68, 0.15);
border: 1px solid rgba(239, 68, 68, 0.3);
border-radius: 12px;
color: #fca5a5;
font-size: 14px;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
:global(.spin) {
animation: spin 1s linear infinite;
}
:global(.spin-slow) {
animation: spin 2s linear infinite;
}
@keyframes pulse {
0%,
100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.7;
transform: scale(1.1);
}
}
:global(.pulse) {
animation: pulse 1.5s ease-in-out infinite;
}
.progress-container {
background: rgba(34, 197, 94, 0.08);
border: 1px solid rgba(34, 197, 94, 0.2);
border-radius: 16px;
padding: 20px;
}
.progress-header {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
color: #4ade80;
font-weight: 700;
font-size: 16px;
margin-bottom: 20px;
}
.progress-steps {
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 20px;
}
.progress-step {
display: flex;
align-items: center;
gap: 12px;
opacity: 0.4;
transition: all 0.3s ease;
}
.progress-step.active {
opacity: 1;
}
.progress-step.current {
opacity: 1;
}
.progress-step.current .step-label {
color: #4ade80;
}
.step-icon {
width: 28px;
height: 28px;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, 0.6);
transition: all 0.3s ease;
}
.progress-step.active .step-icon {
background: rgba(34, 197, 94, 0.2);
color: #4ade80;
}
.progress-step.current .step-icon {
background: #4ade80;
color: #051f18;
}
.step-label {
color: rgba(255, 255, 255, 0.6);
font-size: 13px;
font-weight: 500;
transition: all 0.3s ease;
}
.progress-step.active .step-label {
color: white;
}
.progress-bar {
height: 4px;
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #22c55e, #4ade80);
border-radius: 2px;
transition: width 0.5s ease;
}
@media (min-width: 768px) {
.desktop-bg {
display: block;