diff --git a/config/__pycache__/__init__.cpython-311.pyc b/config/__pycache__/__init__.cpython-311.pyc
index 896bb4f..1fe1dcd 100644
Binary files a/config/__pycache__/__init__.cpython-311.pyc and b/config/__pycache__/__init__.cpython-311.pyc differ
diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc
index d79d6a7..97325ee 100644
Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ
diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc
index 8cf22af..8df0882 100644
Binary files a/config/__pycache__/urls.cpython-311.pyc and b/config/__pycache__/urls.cpython-311.pyc differ
diff --git a/config/__pycache__/wsgi.cpython-311.pyc b/config/__pycache__/wsgi.cpython-311.pyc
index a1b4aa7..2202a48 100644
Binary files a/config/__pycache__/wsgi.cpython-311.pyc and b/config/__pycache__/wsgi.cpython-311.pyc differ
diff --git a/core/__pycache__/__init__.cpython-311.pyc b/core/__pycache__/__init__.cpython-311.pyc
index 3f553f6..fb20891 100644
Binary files a/core/__pycache__/__init__.cpython-311.pyc and b/core/__pycache__/__init__.cpython-311.pyc differ
diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc
index 5e8987a..8f482c6 100644
Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ
diff --git a/core/__pycache__/apps.cpython-311.pyc b/core/__pycache__/apps.cpython-311.pyc
index 2fa4a49..49d5f62 100644
Binary files a/core/__pycache__/apps.cpython-311.pyc and b/core/__pycache__/apps.cpython-311.pyc differ
diff --git a/core/__pycache__/context_processors.cpython-311.pyc b/core/__pycache__/context_processors.cpython-311.pyc
index 75bf223..e1dde4f 100644
Binary files a/core/__pycache__/context_processors.cpython-311.pyc and b/core/__pycache__/context_processors.cpython-311.pyc differ
diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc
new file mode 100644
index 0000000..fe54f61
Binary files /dev/null and b/core/__pycache__/forms.cpython-311.pyc differ
diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc
index a251b5f..cffc914 100644
Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ
diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc
index f705988..821d1f4 100644
Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ
diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc
index 2f0989c..733da46 100644
Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ
diff --git a/core/admin.py b/core/admin.py
index 8c38f3f..0b4c1a6 100644
--- a/core/admin.py
+++ b/core/admin.py
@@ -1,3 +1,43 @@
from django.contrib import admin
-# Register your models here.
+from .models import ActionPlanStep, ProblemCase, RootCause, SolutionOption
+
+
+class RootCauseInline(admin.TabularInline):
+ model = RootCause
+ extra = 0
+
+
+class SolutionOptionInline(admin.TabularInline):
+ model = SolutionOption
+ extra = 0
+ fields = ("rank", "title", "impact", "efficiency", "speed", "low_risk", "decision_score", "success_rate")
+ readonly_fields = ("decision_score",)
+
+
+@admin.register(ProblemCase)
+class ProblemCaseAdmin(admin.ModelAdmin):
+ list_display = ("title", "business_area", "urgency", "priority_score", "financial_impact", "status", "created_at")
+ list_filter = ("business_area", "status", "urgency")
+ search_fields = ("title", "description")
+ inlines = [RootCauseInline, SolutionOptionInline]
+
+
+@admin.register(SolutionOption)
+class SolutionOptionAdmin(admin.ModelAdmin):
+ list_display = ("title", "problem", "rank", "decision_score", "success_rate")
+ list_filter = ("rank",)
+ search_fields = ("title", "problem__title")
+
+
+@admin.register(ActionPlanStep)
+class ActionPlanStepAdmin(admin.ModelAdmin):
+ list_display = ("solution", "day_index", "title", "is_done")
+ list_filter = ("is_done",)
+ search_fields = ("title", "task", "solution__title")
+
+
+@admin.register(RootCause)
+class RootCauseAdmin(admin.ModelAdmin):
+ list_display = ("factor", "problem", "contribution_score", "parent")
+ search_fields = ("factor", "why_chain", "problem__title")
diff --git a/core/forms.py b/core/forms.py
new file mode 100644
index 0000000..ae1e584
--- /dev/null
+++ b/core/forms.py
@@ -0,0 +1,40 @@
+from django import forms
+
+from .models import ProblemCase
+
+
+class ProblemCaseForm(forms.ModelForm):
+ class Meta:
+ model = ProblemCase
+ fields = ["description", "urgency"]
+ labels = {
+ "description": "Masukkan masalah, target, atau hambatan Anda:",
+ "urgency": "Tingkat Urgensi",
+ }
+ widgets = {
+ "description": forms.Textarea(attrs={
+ "class": "form-control problem-textarea",
+ "rows": 9,
+ "placeholder": "Contoh: Kuliah di Singapura atau Jepang, dana 200 juta, ingin 3 tahun selesai.",
+ }),
+ "urgency": forms.TextInput(attrs={
+ "type": "range",
+ "class": "form-range urgency-slider",
+ "min": 1,
+ "max": 5,
+ "step": 1,
+ "oninput": "document.getElementById('urgency-output').value=this.value",
+ }),
+ }
+
+ def clean_description(self):
+ description = self.cleaned_data["description"].strip()
+ if len(description) < 20:
+ raise forms.ValidationError("Tuliskan minimal 20 karakter agar analisis lebih bermakna.")
+ return description
+
+ def clean_urgency(self):
+ urgency = self.cleaned_data["urgency"]
+ if urgency < 1 or urgency > 5:
+ raise forms.ValidationError("Urgensi harus berada pada skala 1 sampai 5.")
+ return urgency
diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py
new file mode 100644
index 0000000..08cde5a
--- /dev/null
+++ b/core/migrations/0001_initial.py
@@ -0,0 +1,82 @@
+# Generated by Django 5.2.7 on 2026-06-19 14:38
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='ProblemCase',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('title', models.CharField(max_length=160, verbose_name='judul kasus')),
+ ('description', models.TextField(verbose_name='deskripsi masalah')),
+ ('business_area', models.CharField(choices=[('sales', 'Penjualan'), ('operations', 'Operasional'), ('finance', 'Keuangan'), ('marketing', 'Marketing'), ('product', 'Produk/Layanan'), ('people', 'Tim & SDM'), ('other', 'Lainnya')], default='sales', max_length=32, verbose_name='area bisnis')),
+ ('urgency', models.PositiveSmallIntegerField(default=3, verbose_name='urgensi')),
+ ('priority_score', models.PositiveSmallIntegerField(default=0, verbose_name='skor prioritas')),
+ ('financial_impact', models.CharField(default='Sedang', max_length=32, verbose_name='dampak finansial')),
+ ('status', models.CharField(choices=[('draft', 'Draft'), ('analyzed', 'Sudah dianalisis')], default='draft', max_length=20)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ],
+ options={
+ 'verbose_name': 'Problem Case',
+ 'verbose_name_plural': 'Problem Cases',
+ 'ordering': ['-created_at'],
+ },
+ ),
+ migrations.CreateModel(
+ name='RootCause',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('factor', models.CharField(max_length=120)),
+ ('contribution_score', models.PositiveSmallIntegerField(default=70)),
+ ('why_chain', models.TextField()),
+ ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='core.rootcause')),
+ ('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='root_causes', to='core.problemcase')),
+ ],
+ options={
+ 'ordering': ['-contribution_score', 'factor'],
+ },
+ ),
+ migrations.CreateModel(
+ name='SolutionOption',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('title', models.CharField(max_length=160)),
+ ('impact', models.PositiveSmallIntegerField(default=70)),
+ ('efficiency', models.PositiveSmallIntegerField(default=70)),
+ ('speed', models.PositiveSmallIntegerField(default=70)),
+ ('low_risk', models.PositiveSmallIntegerField(default=70)),
+ ('decision_score', models.DecimalField(decimal_places=2, default=0, max_digits=5)),
+ ('success_rate', models.PositiveSmallIntegerField(default=70)),
+ ('rank', models.PositiveSmallIntegerField(default=1)),
+ ('rationale', models.TextField()),
+ ('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='solutions', to='core.problemcase')),
+ ],
+ options={
+ 'ordering': ['rank', '-decision_score'],
+ },
+ ),
+ migrations.CreateModel(
+ name='ActionPlanStep',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('day_index', models.PositiveSmallIntegerField()),
+ ('title', models.CharField(max_length=120)),
+ ('task', models.TextField()),
+ ('is_done', models.BooleanField(default=False)),
+ ('solution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='action_steps', to='core.solutionoption')),
+ ],
+ options={
+ 'ordering': ['day_index'],
+ },
+ ),
+ ]
diff --git a/core/migrations/__pycache__/0001_initial.cpython-311.pyc b/core/migrations/__pycache__/0001_initial.cpython-311.pyc
new file mode 100644
index 0000000..773b101
Binary files /dev/null and b/core/migrations/__pycache__/0001_initial.cpython-311.pyc differ
diff --git a/core/migrations/__pycache__/__init__.cpython-311.pyc b/core/migrations/__pycache__/__init__.cpython-311.pyc
index 7995815..7b257e2 100644
Binary files a/core/migrations/__pycache__/__init__.cpython-311.pyc and b/core/migrations/__pycache__/__init__.cpython-311.pyc differ
diff --git a/core/models.py b/core/models.py
index 71a8362..9fe3bc3 100644
--- a/core/models.py
+++ b/core/models.py
@@ -1,3 +1,107 @@
from django.db import models
+from django.urls import reverse
-# Create your models here.
+
+class ProblemCase(models.Model):
+ AREA_SALES = "sales"
+ AREA_OPERATIONS = "operations"
+ AREA_FINANCE = "finance"
+ AREA_MARKETING = "marketing"
+ AREA_PRODUCT = "product"
+ AREA_PEOPLE = "people"
+ AREA_OTHER = "other"
+
+ BUSINESS_AREA_CHOICES = [
+ (AREA_SALES, "Penjualan"),
+ (AREA_OPERATIONS, "Operasional"),
+ (AREA_FINANCE, "Keuangan"),
+ (AREA_MARKETING, "Marketing"),
+ (AREA_PRODUCT, "Produk/Layanan"),
+ (AREA_PEOPLE, "Tim & SDM"),
+ (AREA_OTHER, "Lainnya"),
+ ]
+
+ STATUS_DRAFT = "draft"
+ STATUS_ANALYZED = "analyzed"
+ STATUS_CHOICES = [
+ (STATUS_DRAFT, "Draft"),
+ (STATUS_ANALYZED, "Sudah dianalisis"),
+ ]
+
+ title = models.CharField("judul kasus", max_length=160)
+ description = models.TextField("deskripsi masalah")
+ business_area = models.CharField(
+ "area bisnis", max_length=32, choices=BUSINESS_AREA_CHOICES, default=AREA_SALES
+ )
+ urgency = models.PositiveSmallIntegerField("urgensi", default=3)
+ priority_score = models.PositiveSmallIntegerField("skor prioritas", default=0)
+ financial_impact = models.CharField("dampak finansial", max_length=32, default="Sedang")
+ status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=STATUS_DRAFT)
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ class Meta:
+ ordering = ["-created_at"]
+ verbose_name = "Problem Case"
+ verbose_name_plural = "Problem Cases"
+
+ def __str__(self):
+ return self.title
+
+ def get_absolute_url(self):
+ return reverse("case_detail", kwargs={"pk": self.pk})
+
+
+class RootCause(models.Model):
+ problem = models.ForeignKey(
+ ProblemCase, related_name="root_causes", on_delete=models.CASCADE
+ )
+ parent = models.ForeignKey(
+ "self", related_name="children", null=True, blank=True, on_delete=models.CASCADE
+ )
+ factor = models.CharField(max_length=120)
+ contribution_score = models.PositiveSmallIntegerField(default=70)
+ why_chain = models.TextField()
+
+ class Meta:
+ ordering = ["-contribution_score", "factor"]
+
+ def __str__(self):
+ return f"{self.factor} ({self.contribution_score}%)"
+
+
+class SolutionOption(models.Model):
+ problem = models.ForeignKey(
+ ProblemCase, related_name="solutions", on_delete=models.CASCADE
+ )
+ title = models.CharField(max_length=160)
+ impact = models.PositiveSmallIntegerField(default=70)
+ efficiency = models.PositiveSmallIntegerField(default=70)
+ speed = models.PositiveSmallIntegerField(default=70)
+ low_risk = models.PositiveSmallIntegerField(default=70)
+ decision_score = models.DecimalField(max_digits=5, decimal_places=2, default=0)
+ success_rate = models.PositiveSmallIntegerField(default=70)
+ rank = models.PositiveSmallIntegerField(default=1)
+ rationale = models.TextField()
+
+ class Meta:
+ ordering = ["rank", "-decision_score"]
+
+ def __str__(self):
+ return self.title
+
+
+class ActionPlanStep(models.Model):
+ solution = models.ForeignKey(
+ SolutionOption, related_name="action_steps", on_delete=models.CASCADE
+ )
+ day_index = models.PositiveSmallIntegerField()
+ title = models.CharField(max_length=120)
+ task = models.TextField()
+ is_done = models.BooleanField(default=False)
+
+ class Meta:
+ ordering = ["day_index"]
+
+ def __str__(self):
+ return f"Hari {self.day_index}: {self.title}"
diff --git a/core/templates/base.html b/core/templates/base.html
index 1e7e5fb..734e268 100644
--- a/core/templates/base.html
+++ b/core/templates/base.html
@@ -1,11 +1,13 @@
+{% load static %}
-
+
- {% block title %}Knowledge Base{% endblock %}
+
+ {% block title %}{{ page_title|default:"OPTEMA AI" }}{% endblock %}
+
{% if project_description %}
-
{% endif %}
@@ -13,13 +15,17 @@
{% endif %}
- {% load static %}
+
+
+
+
{% block head %}{% endblock %}
{% block content %}{% endblock %}
+
diff --git a/core/templates/core/_nav.html b/core/templates/core/_nav.html
new file mode 100644
index 0000000..c3b431c
--- /dev/null
+++ b/core/templates/core/_nav.html
@@ -0,0 +1,17 @@
+
+
+
diff --git a/core/templates/core/case_detail.html b/core/templates/core/case_detail.html
new file mode 100644
index 0000000..8bb6269
--- /dev/null
+++ b/core/templates/core/case_detail.html
@@ -0,0 +1,124 @@
+{% extends "base.html" %}
+{% block title %}{{ page_title }}{% endblock %}
+{% block content %}
+{% include "core/_nav.html" %}
+
+
+
+ {% if messages %}
+
+ {% for message in messages %}
+
{{ message }}
+ {% endfor %}
+
+ {% endif %}
+
+
+
Analisis Tersimpan · {{ problem.get_business_area_display }}
+
{{ problem.title }}
+
{{ problem.description }}
+
+
+
+
✅ Analisis Berhasil Disimulasikan!
+
Case ini tersimpan di database lokal dan bisa dibuka ulang dari Riwayat Case.
+
+
+
+
+
+
+
+
+
Problem Detection
+
1. Problem Detection
+
+
Prioritas Skor {{ problem.priority_score }}/100
+
Dampak Finansial {{ problem.financial_impact }}
+
+
+
+
+
+
+
Root Cause Analysis
+
2. Root Cause (Akar Masalah)
+
+ {% for cause in problem.root_causes.all %}
+
+ {{ cause.factor }} {{ cause.contribution_score }}%
+
+ {{ cause.why_chain }}
+
+ {% endfor %}
+
+
+
+
+
+
Solution & Decision Scoring
+
3. Rekomendasi Solusi & Prediksi Sukses
+
Rumus: Decision Score = Impact×0.4 + Efficiency×0.3 + Speed×0.2 + LowRisk×0.1
+
+
+
+
+ Rank
+ Solusi
+ Impact
+ Efficiency
+ Speed
+ Low Risk
+ Sukses Rate
+ Decision Score
+
+
+
+ {% for solution in problem.solutions.all %}
+
+ #{{ solution.rank }}
+
+ {{ solution.title }}
+ {{ solution.rationale }}
+
+ {{ solution.impact }}
+ {{ solution.efficiency }}
+ {{ solution.speed }}
+ {{ solution.low_risk }}
+ {{ solution.success_rate }}%
+ {{ solution.decision_score|floatformat:1 }}
+
+ {% endfor %}
+
+
+
+
+
+ {% if top_solution %}
+
+
Action Plan
+
4. Action Plan 5 Hari: {{ top_solution.title }}
+
+ {% for step in top_solution.action_steps.all %}
+
+ Hari {{ step.day_index }}
+
+
{{ step.title }}
+
{{ step.task }}
+
+
+ {% endfor %}
+
+
+ {% endif %}
+
+
+
+
+
+{% endblock %}
diff --git a/core/templates/core/case_list.html b/core/templates/core/case_list.html
new file mode 100644
index 0000000..e1f9949
--- /dev/null
+++ b/core/templates/core/case_list.html
@@ -0,0 +1,37 @@
+{% extends "base.html" %}
+{% block title %}{{ page_title }}{% endblock %}
+{% block content %}
+{% include "core/_nav.html" %}
+
+
+
+
+
Case Library
+
Daftar Kasus OPTEMA
+
Histori masalah yang sudah diproses menjadi keputusan dan action plan.
+
+
Tambah Kasus Baru
+
+
+ {% for case in cases %}
+
+ {% empty %}
+
+
+
Case library masih kosong.
+
Buat analisis pertama untuk mulai membangun histori keputusan.
+
Mulai Analisis
+
+
+ {% endfor %}
+
+
+
+{% endblock %}
diff --git a/core/templates/core/index.html b/core/templates/core/index.html
index faec813..b4b9bdd 100644
--- a/core/templates/core/index.html
+++ b/core/templates/core/index.html
@@ -1,145 +1,201 @@
{% extends "base.html" %}
+{% load static %}
-{% block title %}{{ project_name }}{% endblock %}
-
-{% block head %}
-
-
-
-
-{% endblock %}
+{% block title %}{{ page_title }}{% endblock %}
{% block content %}
+{% include "core/_nav.html" %}
+
-
-
Analyzing your requirements and generating your app…
-
-
Loading…
+
+
+
+
+
+
+
Mesin Pemecah Masalah Universal & Decision Intelligence Platform
+
💡 OPTEMA AI
+
Optimal, Effective, Efficient Management Assistant
+
MVP single-user tanpa login: masukkan masalah bisnis, keuangan, karier, logistik, teknologi, atau pendidikan; atur urgensi, lalu dapatkan simulasi Problem Detection, Root Cause, Decision Scoring, dan Action Plan yang tersimpan otomatis.
+
+
+ Problem Detection
+ Root Cause
+ Decision Score
+
+
+
+
+
+ Simulasi MVP
+ Prioritas 95/100
+
+
+
Keterbatasan Dana 96%
+
Target 3 Tahun 92%
+
Biaya Hidup 88%
+
Bahasa & Admission 76%
+
+
+
Solusi Teratas
+
Prioritaskan Jepang + Beasiswa
+
Decision Score 83.8 · Sukses Rate 84%
+
+
+
+
- AppWizzy AI is collecting your requirements and applying the first changes.
- This page will refresh automatically as the plan is implemented.
-
- Runtime: Django {{ django_version }} · Python {{ python_version }}
- — UTC {{ current_time|date:"Y-m-d H:i:s" }}
-
-
+
+
+
+
+
MVP Workspace
+
Input kiri, output analisis kanan — seperti prototype Streamlit.
+
+ {% if messages %}
+
+ {% for message in messages %}
+
{{ message }}
+ {% endfor %}
+
+ {% endif %}
+
+
+
+
+
+
+
✅ Analisis Berhasil Disimulasikan!
+
+
+ 1. Problem Detection
+
+
Prioritas Skor 95/100
+
Dampak / Constraint Budget ketat
+
+
+
+
+ 2. Root Cause (Akar Masalah)
+
+
+ Keterbatasan Dana 96%
+
+
+
+ Target Lulus 3 Tahun 92%
+
+
+
+ Biaya Hidup Negara 88%
+
+
+
+ Bahasa & Admission 76%
+
+
+
+
+
+
+ 3. Rekomendasi Solusi & Prediksi Sukses
+ Decision Score = Impact×0.4 + Efficiency×0.3 + Speed×0.2 + LowRisk×0.1
+
+
+
+
+ Solusi
+ Impact
+ Efficiency
+ Speed
+ Low Risk
+ Sukses Rate
+ Decision Score
+
+
+
+ Prioritaskan Jepang + Beasiswa/Part-time Legal 88 86 72 78 84% 83.8
+ Pathway Hemat: Lokal lalu Transfer 76 88 68 86 80% 78.4
+ Singapura Jika Scholarship Besar 82 58 86 55 66% 72.9
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Histori Keputusan
+
Kasus terbaru
+
+
Lihat semua kasus →
+
+
+ {% for case in recent_cases %}
+
+ {% empty %}
+
+
+
Belum ada kasus tersimpan.
+
Gunakan form di atas untuk membuat analisis OPTEMA AI pertama Anda.
+
+
+ {% endfor %}
+
+
+
-
- Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC)
+
+
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/core/urls.py b/core/urls.py
index 6299e3d..3e11c39 100644
--- a/core/urls.py
+++ b/core/urls.py
@@ -1,7 +1,9 @@
from django.urls import path
-from .views import home
+from .views import case_detail, case_list, home
urlpatterns = [
path("", home, name="home"),
+ path("cases/", case_list, name="case_list"),
+ path("cases//", case_detail, name="case_detail"),
]
diff --git a/core/views.py b/core/views.py
index c9aed12..afb8c5b 100644
--- a/core/views.py
+++ b/core/views.py
@@ -1,25 +1,447 @@
-import os
-import platform
+from decimal import Decimal
-from django import get_version as django_version
-from django.shortcuts import render
-from django.utils import timezone
+from django.contrib import messages
+from django.db import transaction
+from django.db.models import Prefetch
+from django.shortcuts import get_object_or_404, redirect, render
+from .forms import ProblemCaseForm
+from .models import ActionPlanStep, ProblemCase, RootCause, SolutionOption
+
+
+def _clamp(value, minimum=1, maximum=100):
+ return max(minimum, min(maximum, int(value)))
+
+
+def _has_any(text, keywords):
+ return any(keyword in text for keyword in keywords)
+
+
+def _decision_score(impact, efficiency, speed, low_risk):
+ return Decimal(str(round((impact * 0.40) + (efficiency * 0.30) + (speed * 0.20) + (low_risk * 0.10), 2)))
+
+
+def _case_title_from_description(description):
+ first_line = " ".join(description.strip().split())
+ if len(first_line) <= 78:
+ return first_line
+ return f"{first_line[:75].rstrip()}..."
+
+
+ANALYSIS_DATABASE = [
+ {
+ "kategori": "Pendidikan",
+ "keyword": [
+ "kuliah", "studi", "study", "kampus", "universitas", "university",
+ "beasiswa", "scholarship", "s1", "s2", "sarjana", "bachelor",
+ "master", "singapura", "singapore", "jepang", "japan", "jlpt", "ielts",
+ ],
+ "penyebab": [
+ "Budget belum cukup untuk total biaya studi",
+ "Pilihan negara/kampus belum dibandingkan dari total cost of study",
+ "Target lulus 3 tahun butuh validasi kurikulum, bahasa, dan admission",
+ ],
+ "solusi": [
+ "Prioritaskan Jepang dengan beasiswa dan kota hemat",
+ "Pilih Singapura hanya jika ada scholarship/subsidy besar",
+ "Buat shortlist program 3 tahun dan simulasi biaya lengkap",
+ ],
+ "impact_label": "Budget ketat",
+ "priority": 50,
+ },
+ {
+ "kategori": "Bisnis",
+ "keyword": ["jualan", "penjualan", "produk", "usaha", "bisnis", "toko"],
+ "penyebab": [
+ "Target pasar tidak tepat",
+ "Promosi kurang efektif",
+ "Produk belum sesuai kebutuhan pasar",
+ ],
+ "solusi": [
+ "Riset pelanggan",
+ "Perbaiki produk",
+ "Optimasi pemasaran",
+ "Bangun channel penjualan",
+ ],
+ "impact_label": "Besar",
+ "priority": 10,
+ },
+ {
+ "kategori": "Keuangan",
+ "keyword": ["uang", "hutang", "utang", "gaji", "tabungan", "biaya"],
+ "penyebab": [
+ "Tidak ada kontrol keuangan",
+ "Pengeluaran terlalu besar",
+ "Pemasukan kurang optimal",
+ ],
+ "solusi": [
+ "Buat anggaran",
+ "Kurangi biaya tidak penting",
+ "Cari sumber pemasukan baru",
+ ],
+ "impact_label": "Cashflow/budget",
+ "priority": 10,
+ },
+ {
+ "kategori": "Karier",
+ "keyword": ["kerja", "pekerjaan", "cv", "skill", "karir", "karier"],
+ "penyebab": [
+ "Skill tidak sesuai kebutuhan",
+ "Kurang pengalaman",
+ "Portofolio belum kuat",
+ ],
+ "solusi": [
+ "Upgrade skill",
+ "Buat portofolio",
+ "Cari peluang kerja sesuai kemampuan",
+ ],
+ "impact_label": "Dampak karier",
+ "priority": 10,
+ },
+ {
+ "kategori": "Logistik",
+ "keyword": ["kirim", "pengiriman", "barang", "gudang", "rute"],
+ "penyebab": [
+ "Rute tidak optimal",
+ "Monitoring kurang",
+ "Proses distribusi lambat",
+ ],
+ "solusi": [
+ "Optimasi rute",
+ "Tracking barang",
+ "Perbaiki sistem distribusi",
+ ],
+ "impact_label": "Operasional",
+ "priority": 10,
+ },
+ {
+ "kategori": "Teknologi",
+ "keyword": ["aplikasi", "website", "coding", "program", "error", "bug"],
+ "penyebab": [
+ "Sistem belum optimal",
+ "Arsitektur kurang tepat",
+ "Bug perangkat lunak",
+ ],
+ "solusi": [
+ "Audit sistem",
+ "Perbaiki kode",
+ "Optimasi teknologi",
+ ],
+ "impact_label": "Teknis/operasional",
+ "priority": 10,
+ },
+]
+
+
+def _detect_problem_category(description):
+ text = description.lower()
+ best_item = None
+ best_score = -1
+
+ for item in ANALYSIS_DATABASE:
+ matches = sum(1 for keyword in item["keyword"] if keyword in text)
+ if matches == 0:
+ continue
+
+ score = matches * 10 + item.get("priority", 0)
+ if item["kategori"] == "Pendidikan" and _has_any(text, ["kuliah", "studi", "kampus", "universitas"]):
+ score += 35
+ if item["kategori"] == "Pendidikan" and _has_any(text, ["singapura", "singapore", "jepang", "japan"]):
+ score += 20
+
+ if score > best_score:
+ best_item = item
+ best_score = score
+
+ return best_item or next(item for item in ANALYSIS_DATABASE if item["kategori"] == "Bisnis")
+
+
+def _is_education_decision(description):
+ text = description.lower()
+ education_keywords = [
+ "kuliah", "studi", "study", "kampus", "universitas", "university",
+ "beasiswa", "scholarship", "s1", "s2", "sarjana", "bachelor",
+ "master", "singapura", "singapore", "jepang", "japan", "jlpt", "ielts",
+ ]
+ constraint_keywords = [
+ "dana", "budget", "biaya", "uang", "200 juta", "200jt", "3 tahun",
+ "tiga tahun", "kelar", "lulus", "visa",
+ ]
+ return _has_any(text, education_keywords) and _has_any(text, constraint_keywords)
+
+
+def _infer_business_area(description):
+ text = description.lower()
+ kategori = _detect_problem_category(description)["kategori"]
+
+ if kategori == "Pendidikan":
+ return ProblemCase.AREA_OTHER
+ if kategori == "Keuangan":
+ return ProblemCase.AREA_FINANCE
+ if kategori == "Karier":
+ return ProblemCase.AREA_PEOPLE
+ if kategori in {"Logistik", "Teknologi"}:
+ return ProblemCase.AREA_OPERATIONS
+ if _has_any(text, ["iklan", "marketing", "konten", "campaign", "kampanye", "promosi"]):
+ return ProblemCase.AREA_MARKETING
+ if _has_any(text, ["produk", "layanan", "fitur", "kualitas"]):
+ return ProblemCase.AREA_PRODUCT
+ return ProblemCase.AREA_SALES
+
+
+def _write_analysis_records(problem, financial_impact, cause_profiles, options, steps):
+ problem.root_causes.all().delete()
+ problem.solutions.all().delete()
+
+ problem.priority_score = _clamp(80 + problem.urgency * 3)
+ problem.financial_impact = financial_impact
+ problem.status = ProblemCase.STATUS_ANALYZED
+ problem.save(update_fields=["priority_score", "financial_impact", "status", "updated_at"])
+
+ RootCause.objects.bulk_create([
+ RootCause(
+ problem=problem,
+ factor=factor,
+ contribution_score=_clamp(score),
+ why_chain=why_chain,
+ )
+ for factor, score, why_chain in cause_profiles
+ ])
+
+ scored = []
+ for option in options:
+ scored.append({
+ **option,
+ "decision_score": _decision_score(
+ option["impact"],
+ option["efficiency"],
+ option["speed"],
+ option["low_risk"],
+ ),
+ })
+ scored.sort(key=lambda item: item["decision_score"], reverse=True)
+
+ created_solutions = SolutionOption.objects.bulk_create([
+ SolutionOption(
+ problem=problem,
+ title=option["title"],
+ impact=option["impact"],
+ efficiency=option["efficiency"],
+ speed=option["speed"],
+ low_risk=option["low_risk"],
+ decision_score=option["decision_score"],
+ success_rate=option["success_rate"],
+ rank=rank,
+ rationale=option["rationale"],
+ )
+ for rank, option in enumerate(scored, start=1)
+ ])
+
+ top_solution = created_solutions[0]
+ ActionPlanStep.objects.bulk_create([
+ ActionPlanStep(solution=top_solution, day_index=day, title=title, task=task)
+ for day, title, task in steps
+ ])
+
+
+def _build_education_analysis(problem):
+ cause_profiles = [
+ (
+ "Keterbatasan Dana",
+ 96,
+ "Budget perlu menutup tuition, biaya hidup, visa, tiket, asuransi, dan dana darurat; jadi pilihan negara/kampus harus disaring dari batas biaya dulu.",
+ ),
+ (
+ "Target Lulus 3 Tahun",
+ 92,
+ "Target selesai cepat hanya realistis jika program, credit transfer, kalender akademik, dan syarat kelulusan cocok sejak awal.",
+ ),
+ (
+ "Biaya Hidup Negara",
+ 88,
+ "Perbandingan Singapura vs Jepang harus dihitung dari total cost of study, bukan hanya uang kuliah; kota, tempat tinggal, dan transport sangat menentukan.",
+ ),
+ (
+ "Bahasa & Admission",
+ 76,
+ "Risiko gagal masuk atau molor muncul jika syarat IELTS/JLPT, dokumen, deadline, dan kesiapan bahasa belum dipetakan.",
+ ),
+ ]
+ options = [
+ {
+ "title": "Prioritaskan Jepang + Beasiswa/Part-time Legal",
+ "impact": 88,
+ "efficiency": 86,
+ "speed": 72,
+ "low_risk": 78,
+ "success_rate": 84,
+ "rationale": "Lebih masuk akal untuk budget ketat bila shortlist difokuskan ke kampus/program yang punya beasiswa, kota yang lebih hemat, dan opsi kerja paruh waktu sesuai aturan visa.",
+ },
+ {
+ "title": "Singapura Hanya Jika Ada Scholarship/Subsidy Besar",
+ "impact": 82,
+ "efficiency": 58,
+ "speed": 86,
+ "low_risk": 55,
+ "success_rate": 66,
+ "rationale": "Singapura bisa unggul untuk akses industri dan durasi cepat, tetapi budget 200 juta berisiko tidak cukup tanpa bantuan biaya yang jelas sejak awal.",
+ },
+ {
+ "title": "Pathway Hemat: Mulai Lokal lalu Transfer/Credit Recognition",
+ "impact": 76,
+ "efficiency": 88,
+ "speed": 68,
+ "low_risk": 86,
+ "success_rate": 80,
+ "rationale": "Menekan cash-out awal sambil mengejar beasiswa dan kesiapan bahasa, tetapi harus dipastikan kredit bisa diakui agar target total 3 tahun tidak mundur.",
+ },
+ ]
+ steps = [
+ (1, "Buat batas biaya final", "Pecah dana 200 juta menjadi tuition, living cost, visa, tiket, asuransi, dan dana darurat; coret opsi yang melewati batas aman."),
+ (2, "Shortlist program 3 tahun", "Cari minimal 10 program Jepang dan 3 program Singapura yang durasinya cocok, lalu tandai syarat bahasa, deadline, dan total biaya."),
+ (3, "Kejar funding", "Daftar beasiswa, tuition waiver, atau sponsor; jangan memilih Singapura kecuali kekurangan biaya sudah tertutup jelas."),
+ (4, "Validasi aturan visa", "Cek izin kerja paruh waktu, syarat dokumen finansial, serta risiko jika kurs/biaya hidup naik."),
+ (5, "Decision gate", "Pilih Jepang jika total biaya paling aman; pilih Singapura hanya jika scholarship/subsidy membuat biaya 3 tahun masuk budget."),
+ ]
+ _write_analysis_records(problem, "Budget ketat", cause_profiles, options, steps)
+
+
+def _build_category_analysis(problem, category):
+ kategori = category["kategori"]
+ causes = category["penyebab"]
+ solutions = category["solusi"]
+
+ cause_profiles = [
+ (
+ cause,
+ _clamp(92 - index * 8),
+ f"Input terdeteksi sebagai kategori {kategori}. Penyebab ini perlu divalidasi karena dapat langsung memengaruhi target, batasan, dan keputusan berikutnya.",
+ )
+ for index, cause in enumerate(causes)
+ ]
+
+ options = []
+ for index, solution in enumerate(solutions):
+ options.append({
+ "title": solution,
+ "impact": _clamp(88 - index * 4),
+ "efficiency": _clamp(82 + (index % 2) * 5 - index * 2),
+ "speed": _clamp(84 - index * 5),
+ "low_risk": _clamp(74 + index * 4),
+ "success_rate": _clamp(82 - index * 3),
+ "rationale": f"Solusi ini cocok untuk kategori {kategori} karena langsung menargetkan penyebab utama: {causes[min(index, len(causes) - 1)]}.",
+ })
+
+ steps = [
+ (1, "Kunci tujuan dan batasan", f"Kategori utama: {kategori}. Tulis target akhir, batasan dana/waktu, dan indikator sukses yang terukur."),
+ (2, "Validasi penyebab utama", f"Cek apakah penyebab paling dominan adalah: {causes[0]}. Kumpulkan bukti sederhana sebelum memilih solusi."),
+ (3, "Bandingkan opsi solusi", f"Bandingkan opsi: {', '.join(solutions[:3])}. Pilih yang paling sesuai dengan budget, waktu, dan risiko."),
+ (4, "Jalankan langkah kecil", "Mulai dari eksperimen paling kecil selama 1-3 hari agar cepat terlihat apakah arah solusi benar."),
+ (5, "Evaluasi dan putuskan", "Bandingkan hasil dengan indikator sukses, lalu pilih lanjutkan, ubah strategi, atau hentikan opsi yang tidak efektif."),
+ ]
+ _write_analysis_records(problem, category.get("impact_label", "Sedang"), cause_profiles, options, steps)
+
+
+def _build_business_analysis(problem):
+ cause_profiles = [
+ ("Marketing", 95, "Akuisisi dan pesan penjualan belum cukup tajam untuk menjangkau segmen yang paling siap membeli."),
+ ("Kompetitor", 88, "Penawaran kompetitor terlihat lebih kuat sehingga pelanggan membandingkan harga, bonus, dan bukti hasil."),
+ ("Harga", 85, "Persepsi value belum seimbang dengan harga; bundling, bonus, atau bukti manfaat perlu diperjelas."),
+ ("Produk", 70, "Produk/layanan perlu validasi ulang dari feedback pelanggan agar lebih relevan dengan kebutuhan saat ini."),
+ ]
+ options = [
+ {
+ "title": "Optimasi Iklan Digital",
+ "impact": 90,
+ "efficiency": 70,
+ "speed": 85,
+ "low_risk": 60,
+ "success_rate": 85,
+ "rationale": "Perbaiki targeting, materi iklan, dan pesan promosi agar trafik yang masuk lebih relevan dan cepat terukur.",
+ },
+ {
+ "title": "Program Reseller & Agen",
+ "impact": 85,
+ "efficiency": 80,
+ "speed": 60,
+ "low_risk": 75,
+ "success_rate": 92,
+ "rationale": "Bangun kanal distribusi rendah biaya melalui mitra yang sudah punya relasi dan kepercayaan dengan calon pembeli.",
+ },
+ {
+ "title": "Diskon Bundling Akhir Bulan",
+ "impact": 70,
+ "efficiency": 60,
+ "speed": 95,
+ "low_risk": 90,
+ "success_rate": 70,
+ "rationale": "Dorong keputusan pembelian cepat dengan paket bernilai tinggi, periode terbatas, dan risiko operasional rendah.",
+ },
+ ]
+ steps = [
+ (1, "Audit data cepat", "Kumpulkan angka penjualan, biaya iklan, channel, produk terlaris, dan keluhan pelanggan 90 hari terakhir."),
+ (2, "Validasi akar masalah", "Bandingkan temuan dengan faktor Marketing, Kompetitor, Harga, dan Produk; pilih 1–2 penyebab paling dominan."),
+ (3, "Luncurkan eksperimen", "Jalankan eksperimen kecil untuk solusi teratas dengan target metrik, owner, dan batas anggaran yang jelas."),
+ (4, "Pantau respon pasar", "Review metrik harian: leads, conversion rate, biaya per transaksi, repeat order, dan risiko operasional."),
+ (5, "Putuskan scale/stop", "Bandingkan hasil dengan baseline lalu pilih: scale, iterasi pesan/offer, atau hentikan eksperimen."),
+ ]
+ _write_analysis_records(problem, "Besar", cause_profiles, options, steps)
+
+
+def build_case_analysis(problem):
+ """Create deterministic MVP analysis records with category-aware rules."""
+ category = _detect_problem_category(problem.description)
+
+ if category["kategori"] == "Pendidikan" and _is_education_decision(problem.description):
+ _build_education_analysis(problem)
+ return
+
+ _build_category_analysis(problem, category)
def home(request):
- """Render the landing screen with loader and environment details."""
- host_name = request.get_host().lower()
- agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic"
- now = timezone.now()
+ form = ProblemCaseForm(request.POST or None)
+ if request.method == "POST":
+ if form.is_valid():
+ with transaction.atomic():
+ problem = form.save(commit=False)
+ problem.title = _case_title_from_description(problem.description)
+ problem.business_area = _infer_business_area(problem.description)
+ problem.status = ProblemCase.STATUS_DRAFT
+ problem.save()
+ build_case_analysis(problem)
+ messages.success(request, "✅ Analisis berhasil disimulasikan dan disimpan sebagai case.")
+ return redirect(problem.get_absolute_url())
+ messages.error(request, "Mohon periksa input. Deskripsi masalah perlu cukup lengkap agar analisis akurat.")
+ recent_cases = ProblemCase.objects.all()[:4]
context = {
- "project_name": "New Style",
- "agent_brand": agent_brand,
- "django_version": django_version(),
- "python_version": platform.python_version(),
- "current_time": now,
- "host_name": host_name,
- "project_description": os.getenv("PROJECT_DESCRIPTION", ""),
- "project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
+ "form": form,
+ "recent_cases": recent_cases,
+ "page_title": "OPTEMA AI — MVP Decision Intelligence",
+ "meta_description": "OPTEMA AI membantu mengubah masalah bisnis, keuangan, karier, logistik, teknologi, dan pendidikan menjadi problem detection, root-cause analysis, decision scoring, dan action plan.",
}
return render(request, "core/index.html", context)
+
+
+def case_list(request):
+ cases = ProblemCase.objects.all()
+ return render(request, "core/case_list.html", {
+ "cases": cases,
+ "page_title": "Daftar Kasus — OPTEMA AI",
+ "meta_description": "Lihat histori kasus yang sudah dianalisis oleh OPTEMA AI.",
+ })
+
+
+def case_detail(request, pk):
+ action_steps = Prefetch("solutions__action_steps", queryset=ActionPlanStep.objects.all())
+ problem = get_object_or_404(
+ ProblemCase.objects.prefetch_related("root_causes", "solutions", action_steps),
+ pk=pk,
+ )
+ top_solution = problem.solutions.first()
+ return render(request, "core/case_detail.html", {
+ "problem": problem,
+ "top_solution": top_solution,
+ "page_title": f"{problem.title} — Analisis OPTEMA AI",
+ "meta_description": f"Analisis prioritas, akar masalah, solusi berskor, dan action plan untuk {problem.title}.",
+ })
diff --git a/static/css/custom.css b/static/css/custom.css
index 925f6ed..78f21a0 100644
--- a/static/css/custom.css
+++ b/static/css/custom.css
@@ -1,4 +1,429 @@
-/* Custom styles for the application */
-body {
- font-family: system-ui, -apple-system, sans-serif;
+/* OPTEMA AI custom brand system */
+:root {
+ --optema-ink: #14213d;
+ --optema-muted: #627084;
+ --optema-primary: #0f8b8d;
+ --optema-primary-dark: #0b6768;
+ --optema-secondary: #f6ae2d;
+ --optema-accent: #f26419;
+ --optema-mint: #dff8f5;
+ --optema-sand: #fff6e4;
+ --optema-cloud: #f7fafc;
+ --optema-white: #ffffff;
+ --optema-line: #dce8ee;
+ --optema-shadow: 0 24px 70px rgba(20, 33, 61, 0.14);
+}
+
+* { box-sizing: border-box; }
+html { scroll-behavior: smooth; }
+body {
+ margin: 0;
+ font-family: 'Inter', system-ui, -apple-system, sans-serif;
+ color: var(--optema-ink);
+ background:
+ radial-gradient(circle at 8% 4%, rgba(15, 139, 141, 0.14), transparent 28rem),
+ radial-gradient(circle at 95% 8%, rgba(246, 174, 45, 0.16), transparent 24rem),
+ var(--optema-cloud);
+}
+
+h1, h2, h3, .navbar-brand {
+ font-family: 'Plus Jakarta Sans', 'Inter', sans-serif;
+ letter-spacing: -0.04em;
+}
+
+a { color: inherit; }
+a:focus-visible, button:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-visible {
+ outline: 3px solid rgba(246, 174, 45, 0.65) !important;
+ outline-offset: 3px;
+}
+
+.optema-nav {
+ position: sticky;
+ top: 0;
+ z-index: 20;
+ background: rgba(247, 250, 252, 0.82);
+ backdrop-filter: blur(18px);
+ border-bottom: 1px solid rgba(220, 232, 238, 0.82);
+}
+.brand-mark { font-weight: 800; color: var(--optema-ink); }
+.brand-icon {
+ display: inline-grid;
+ place-items: center;
+ width: 2.15rem; height: 2.15rem;
+ margin-right: .45rem;
+ border-radius: 14px;
+ color: white;
+ background: linear-gradient(135deg, var(--optema-primary), var(--optema-accent));
+ box-shadow: 0 12px 24px rgba(15, 139, 141, 0.24);
+}
+.nav-link { font-weight: 700; color: var(--optema-muted); }
+.nav-link:hover, .nav-link:focus { color: var(--optema-primary); }
+.admin-link {
+ border: 1px solid var(--optema-line);
+ border-radius: 999px;
+ padding-inline: 1rem !important;
+ background: rgba(255,255,255,.7);
+}
+
+.hero-section {
+ min-height: 78vh;
+ display: flex;
+ align-items: center;
+ background:
+ linear-gradient(145deg, rgba(223,248,245,.88), rgba(255,246,228,.72) 55%, rgba(255,255,255,.9)),
+ var(--optema-cloud);
+}
+.shape {
+ position: absolute; border-radius: 999px; filter: blur(.2px); opacity: .82;
+}
+.shape-one {
+ width: 15rem; height: 15rem; right: 6%; top: 12%;
+ background: linear-gradient(135deg, rgba(15,139,141,.2), rgba(246,174,45,.38));
+}
+.shape-two {
+ width: 9rem; height: 9rem; left: 4%; bottom: 8%;
+ background: linear-gradient(135deg, rgba(242,100,25,.22), rgba(15,139,141,.2));
+}
+.eyebrow, .section-kicker {
+ color: var(--optema-primary-dark);
+ text-transform: uppercase;
+ letter-spacing: .12em;
+ font-size: .78rem;
+ font-weight: 800;
+}
+.display-heading {
+ font-size: clamp(2.7rem, 6vw, 5.9rem);
+ line-height: .95;
+ font-weight: 800;
+}
+.hero-copy, .section-copy {
+ color: var(--optema-muted);
+ font-size: clamp(1rem, 1vw + .76rem, 1.2rem);
+ line-height: 1.75;
+}
+.btn-optema {
+ border: 0;
+ color: #fff;
+ font-weight: 800;
+ border-radius: 999px;
+ padding: .9rem 1.35rem;
+ background: linear-gradient(135deg, var(--optema-primary), var(--optema-primary-dark));
+ box-shadow: 0 14px 35px rgba(15, 139, 141, .26);
+}
+.btn-optema:hover, .btn-optema:focus { color:#fff; transform: translateY(-1px); box-shadow: 0 18px 44px rgba(15,139,141,.32); }
+.btn-outline-optema {
+ border: 1px solid rgba(15,139,141,.3);
+ color: var(--optema-primary-dark);
+ background: rgba(255,255,255,.68);
+ font-weight: 800;
+ border-radius: 999px;
+ padding: .9rem 1.35rem;
+}
+.btn-outline-optema:hover, .btn-outline-optema:focus { background: var(--optema-mint); color: var(--optema-primary-dark); }
+.trust-strip { display: flex; flex-wrap: wrap; gap: .65rem; }
+.trust-strip span, .score-pill, .status-dot {
+ display: inline-flex; align-items: center; gap: .45rem;
+ border: 1px solid rgba(15,139,141,.16);
+ background: rgba(255,255,255,.72);
+ border-radius: 999px;
+ padding: .55rem .8rem;
+ color: var(--optema-muted);
+ font-weight: 800;
+ font-size: .86rem;
+}
+.status-dot i { width: .55rem; height: .55rem; border-radius: 999px; background: #23c483; box-shadow: 0 0 0 6px rgba(35,196,131,.15); }
+.glass-card, .form-shell, .analysis-card, .empty-state, .case-card, .metric-card {
+ border: 1px solid rgba(220, 232, 238, .9);
+ background: rgba(255,255,255,.82);
+ backdrop-filter: blur(20px);
+ box-shadow: var(--optema-shadow);
+ border-radius: 32px;
+}
+.decision-panel { padding: clamp(1.35rem, 3vw, 2rem); }
+.mini-chart { display: grid; gap: 1rem; }
+.mini-chart div {
+ --bar: 70%;
+ position: relative; overflow: hidden;
+ display: flex; justify-content: space-between;
+ padding: .9rem 1rem;
+ border-radius: 18px;
+ background: #eef6f7;
+ font-weight: 800;
+}
+.mini-chart div::before {
+ content: ''; position: absolute; inset: 0 auto 0 0; width: var(--bar);
+ background: linear-gradient(90deg, rgba(15,139,141,.24), rgba(246,174,45,.28));
+}
+.mini-chart span, .mini-chart b { position: relative; z-index: 1; }
+.bar-95 { --bar: 95% !important; } .bar-88 { --bar: 88% !important; } .bar-82 { --bar: 82% !important; }
+.solution-preview {
+ padding: 1.2rem; border-radius: 24px;
+ background: linear-gradient(135deg, var(--optema-ink), #183b56); color: white;
+}
+.solution-preview h2 { font-size: 1.35rem; }
+.solution-preview p { color: rgba(255,255,255,.74); margin-bottom: 0; }
+.small-label { color: var(--optema-secondary) !important; text-transform: uppercase; font-weight: 800; letter-spacing: .12em; font-size: .75rem; }
+
+.section-pad { padding: clamp(4rem, 8vw, 7rem) 0; }
+.section-title, .page-title { font-size: clamp(2rem, 3.5vw, 3.6rem); font-weight: 800; line-height: 1.02; }
+.form-shell { padding: clamp(1.25rem, 3vw, 2rem); }
+.form-control, .form-select { border: 1px solid var(--optema-line); border-radius: 18px; padding: .9rem 1rem; }
+.form-control:focus, .form-select:focus { border-color: var(--optema-primary); box-shadow: 0 0 0 .22rem rgba(15,139,141,.13); }
+.form-label { font-weight: 800; color: var(--optema-ink); }
+.form-text { color: var(--optema-muted); }
+.feature-list { display: grid; gap: .85rem; margin-top: 1.5rem; }
+.feature-list div { display: flex; gap: .8rem; align-items: center; color: var(--optema-muted); }
+.feature-list strong {
+ display:grid; place-items:center; width:2rem; height:2rem; border-radius:12px;
+ background: var(--optema-sand); color: var(--optema-accent);
+}
+.case-card {
+ display: flex; flex-direction: column; gap: .9rem;
+ padding: 1.35rem; text-decoration: none; transition: .2s ease;
+}
+.case-card:hover, .case-card:focus { transform: translateY(-4px); border-color: rgba(15,139,141,.35); color: var(--optema-ink); }
+.case-card h2, .case-card h3 { font-size: 1.15rem; font-weight: 800; margin: 0; }
+.case-card p { color: var(--optema-muted); margin: 0; line-height: 1.65; }
+.case-area { color: var(--optema-primary-dark); font-size: .78rem; font-weight: 900; text-transform: uppercase; letter-spacing: .1em; }
+.case-meta { display: flex; flex-wrap: wrap; justify-content: space-between; gap: .7rem; color: var(--optema-muted); font-weight: 800; font-size: .86rem; margin-top: auto; }
+.empty-state { padding: 2rem; text-align: center; }
+.empty-state p { color: var(--optema-muted); }
+.link-optema { color: var(--optema-primary-dark); font-weight: 900; text-decoration: none; }
+.link-optema:hover { color: var(--optema-accent); }
+.site-footer {
+ padding: 1.5rem 0; color: var(--optema-muted);
+ border-top: 1px solid var(--optema-line); background: rgba(255,255,255,.68);
+}
+.page-shell { min-height: 100vh; background: linear-gradient(180deg, rgba(223,248,245,.55), transparent 24rem); }
+.detail-hero { padding: clamp(3rem, 6vw, 5rem) 0; background: linear-gradient(135deg, rgba(223,248,245,.9), rgba(255,246,228,.85)); }
+.metric-grid { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 1rem; }
+.metric-card { padding: 1.25rem; }
+.metric-card span { color: var(--optema-muted); font-weight: 800; display:block; }
+.metric-card strong { font-family:'Plus Jakarta Sans'; font-size: 1.75rem; }
+.analysis-card { padding: clamp(1.2rem, 2.6vw, 2rem); }
+.analysis-card h2 { font-weight: 800; margin-bottom: 1.2rem; }
+.cause-list { display: grid; gap: 1.2rem; }
+.cause-list article p { color: var(--optema-muted); margin: .6rem 0 0; line-height: 1.6; }
+.progress { height: .65rem; background: #e6f2f3; border-radius: 999px; margin-top: .55rem; }
+.progress-bar { background: linear-gradient(90deg, var(--optema-primary), var(--optema-secondary)); }
+.solution-table { --bs-table-bg: transparent; }
+.solution-table th { color: var(--optema-muted); text-transform: uppercase; letter-spacing: .08em; font-size: .78rem; }
+.solution-table td { padding: 1rem .75rem; }
+.solution-table p { color: var(--optema-muted); margin: .25rem 0; }
+.solution-table small { color: var(--optema-primary-dark); font-weight: 800; }
+.rank-badge { display: inline-flex; border-radius: 999px; background: var(--optema-sand); color: var(--optema-accent); padding: .35rem .6rem; font-weight: 900; }
+.action-card { background: linear-gradient(145deg, rgba(255,255,255,.92), rgba(223,248,245,.76)); }
+.timeline { display: grid; gap: 1rem; }
+.timeline article { display: grid; grid-template-columns: 5rem 1fr; gap: 1rem; align-items: start; }
+.timeline article > span {
+ display: inline-flex; justify-content:center; padding: .45rem .65rem; border-radius: 999px;
+ background: var(--optema-ink); color: white; font-weight: 900; font-size: .82rem;
+}
+.timeline h3 { font-size: 1.05rem; font-weight: 800; margin-bottom: .2rem; }
+.timeline p { color: var(--optema-muted); margin: 0; line-height: 1.6; }
+
+.score-width-0 { width: 0%; }
+.score-width-1 { width: 1%; }
+.score-width-2 { width: 2%; }
+.score-width-3 { width: 3%; }
+.score-width-4 { width: 4%; }
+.score-width-5 { width: 5%; }
+.score-width-6 { width: 6%; }
+.score-width-7 { width: 7%; }
+.score-width-8 { width: 8%; }
+.score-width-9 { width: 9%; }
+.score-width-10 { width: 10%; }
+.score-width-11 { width: 11%; }
+.score-width-12 { width: 12%; }
+.score-width-13 { width: 13%; }
+.score-width-14 { width: 14%; }
+.score-width-15 { width: 15%; }
+.score-width-16 { width: 16%; }
+.score-width-17 { width: 17%; }
+.score-width-18 { width: 18%; }
+.score-width-19 { width: 19%; }
+.score-width-20 { width: 20%; }
+.score-width-21 { width: 21%; }
+.score-width-22 { width: 22%; }
+.score-width-23 { width: 23%; }
+.score-width-24 { width: 24%; }
+.score-width-25 { width: 25%; }
+.score-width-26 { width: 26%; }
+.score-width-27 { width: 27%; }
+.score-width-28 { width: 28%; }
+.score-width-29 { width: 29%; }
+.score-width-30 { width: 30%; }
+.score-width-31 { width: 31%; }
+.score-width-32 { width: 32%; }
+.score-width-33 { width: 33%; }
+.score-width-34 { width: 34%; }
+.score-width-35 { width: 35%; }
+.score-width-36 { width: 36%; }
+.score-width-37 { width: 37%; }
+.score-width-38 { width: 38%; }
+.score-width-39 { width: 39%; }
+.score-width-40 { width: 40%; }
+.score-width-41 { width: 41%; }
+.score-width-42 { width: 42%; }
+.score-width-43 { width: 43%; }
+.score-width-44 { width: 44%; }
+.score-width-45 { width: 45%; }
+.score-width-46 { width: 46%; }
+.score-width-47 { width: 47%; }
+.score-width-48 { width: 48%; }
+.score-width-49 { width: 49%; }
+.score-width-50 { width: 50%; }
+.score-width-51 { width: 51%; }
+.score-width-52 { width: 52%; }
+.score-width-53 { width: 53%; }
+.score-width-54 { width: 54%; }
+.score-width-55 { width: 55%; }
+.score-width-56 { width: 56%; }
+.score-width-57 { width: 57%; }
+.score-width-58 { width: 58%; }
+.score-width-59 { width: 59%; }
+.score-width-60 { width: 60%; }
+.score-width-61 { width: 61%; }
+.score-width-62 { width: 62%; }
+.score-width-63 { width: 63%; }
+.score-width-64 { width: 64%; }
+.score-width-65 { width: 65%; }
+.score-width-66 { width: 66%; }
+.score-width-67 { width: 67%; }
+.score-width-68 { width: 68%; }
+.score-width-69 { width: 69%; }
+.score-width-70 { width: 70%; }
+.score-width-71 { width: 71%; }
+.score-width-72 { width: 72%; }
+.score-width-73 { width: 73%; }
+.score-width-74 { width: 74%; }
+.score-width-75 { width: 75%; }
+.score-width-76 { width: 76%; }
+.score-width-77 { width: 77%; }
+.score-width-78 { width: 78%; }
+.score-width-79 { width: 79%; }
+.score-width-80 { width: 80%; }
+.score-width-81 { width: 81%; }
+.score-width-82 { width: 82%; }
+.score-width-83 { width: 83%; }
+.score-width-84 { width: 84%; }
+.score-width-85 { width: 85%; }
+.score-width-86 { width: 86%; }
+.score-width-87 { width: 87%; }
+.score-width-88 { width: 88%; }
+.score-width-89 { width: 89%; }
+.score-width-90 { width: 90%; }
+.score-width-91 { width: 91%; }
+.score-width-92 { width: 92%; }
+.score-width-93 { width: 93%; }
+.score-width-94 { width: 94%; }
+.score-width-95 { width: 95%; }
+.score-width-96 { width: 96%; }
+.score-width-97 { width: 97%; }
+.score-width-98 { width: 98%; }
+.score-width-99 { width: 99%; }
+.score-width-100 { width: 100%; }
+
+@media (max-width: 767px) {
+ .metric-grid { grid-template-columns: 1fr; }
+ .timeline article { grid-template-columns: 1fr; }
+ .hero-section { min-height: auto; }
+}
+
+
+/* Streamlit-inspired MVP workspace */
+.hero-subtitle {
+ font-family: 'Plus Jakarta Sans', 'Inter', sans-serif;
+ font-size: clamp(1.25rem, 1.2vw + 1rem, 2rem);
+ font-weight: 800;
+ color: var(--optema-primary-dark);
+ letter-spacing: -0.03em;
+}
+.mvp-hero .display-heading { letter-spacing: -0.07em; }
+.mvp-workspace .sticky-lg-top { top: 6rem; }
+.panel-heading {
+ display: flex;
+ gap: 1rem;
+ align-items: flex-start;
+ margin-bottom: 1.4rem;
+}
+.panel-heading h2 {
+ font-size: 1.45rem;
+ margin: 0 0 .25rem;
+ font-weight: 900;
+}
+.panel-heading p,
+.form-note,
+.formula-note,
+.detail-status-card p {
+ color: var(--optema-muted);
+ line-height: 1.65;
+}
+.panel-icon {
+ display: inline-grid;
+ place-items: center;
+ width: 3rem;
+ height: 3rem;
+ border-radius: 18px;
+ background: linear-gradient(135deg, var(--optema-mint), var(--optema-sand));
+ box-shadow: 0 16px 28px rgba(15, 139, 141, .12);
+ font-size: 1.35rem;
+}
+.problem-textarea {
+ min-height: 15rem;
+ resize: vertical;
+ border-radius: 22px;
+ border-color: rgba(15,139,141,.18);
+ background: rgba(255,255,255,.82);
+}
+.problem-textarea:focus {
+ border-color: var(--optema-primary);
+ box-shadow: 0 0 0 .25rem rgba(15,139,141,.12);
+}
+.urgency-slider { accent-color: var(--optema-primary); }
+.urgency-value {
+ min-width: 2.4rem;
+ text-align: center;
+ padding: .35rem .7rem;
+ border-radius: 999px;
+ color: #fff;
+ background: var(--optema-ink);
+ font-weight: 900;
+}
+.range-labels {
+ display: flex;
+ justify-content: space-between;
+ color: var(--optema-muted);
+ font-size: .82rem;
+ font-weight: 800;
+ margin-top: -.3rem;
+}
+.output-panel { overflow: hidden; }
+.output-success {
+ display: inline-flex;
+ align-items: center;
+ gap: .5rem;
+ color: #087447;
+ background: rgba(35, 196, 131, .12);
+ border: 1px solid rgba(35, 196, 131, .22);
+ border-radius: 999px;
+ padding: .7rem 1rem;
+ font-weight: 900;
+}
+.output-section {
+ padding: 1.25rem 0;
+ border-top: 1px solid var(--optema-line);
+}
+.output-section:first-of-type { border-top: 0; padding-top: 0; }
+.output-section h2 { font-size: clamp(1.25rem, 1vw + 1rem, 1.8rem); }
+.compact-cause-list { gap: .85rem; }
+.compact-cause-list article p { display: none; }
+.preview-table th,
+.preview-table td { white-space: nowrap; }
+.detail-status-card { padding: 1.35rem; }
+.bar-85 { --bar: 85% !important; }
+.bar-70 { --bar: 70% !important; }
+
+@media (max-width: 991px) {
+ .mvp-workspace .sticky-lg-top { position: static; }
}
diff --git a/staticfiles/css/custom.css b/staticfiles/css/custom.css
index 108056f..78f21a0 100644
--- a/staticfiles/css/custom.css
+++ b/staticfiles/css/custom.css
@@ -1,21 +1,429 @@
-
+/* OPTEMA AI custom brand system */
:root {
- --bg-color-start: #6a11cb;
- --bg-color-end: #2575fc;
- --text-color: #ffffff;
- --card-bg-color: rgba(255, 255, 255, 0.01);
- --card-border-color: rgba(255, 255, 255, 0.1);
+ --optema-ink: #14213d;
+ --optema-muted: #627084;
+ --optema-primary: #0f8b8d;
+ --optema-primary-dark: #0b6768;
+ --optema-secondary: #f6ae2d;
+ --optema-accent: #f26419;
+ --optema-mint: #dff8f5;
+ --optema-sand: #fff6e4;
+ --optema-cloud: #f7fafc;
+ --optema-white: #ffffff;
+ --optema-line: #dce8ee;
+ --optema-shadow: 0 24px 70px rgba(20, 33, 61, 0.14);
}
+
+* { box-sizing: border-box; }
+html { scroll-behavior: smooth; }
body {
- margin: 0;
- font-family: 'Inter', sans-serif;
- background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
- color: var(--text-color);
- display: flex;
- justify-content: center;
- align-items: center;
- min-height: 100vh;
- text-align: center;
- overflow: hidden;
- position: relative;
+ margin: 0;
+ font-family: 'Inter', system-ui, -apple-system, sans-serif;
+ color: var(--optema-ink);
+ background:
+ radial-gradient(circle at 8% 4%, rgba(15, 139, 141, 0.14), transparent 28rem),
+ radial-gradient(circle at 95% 8%, rgba(246, 174, 45, 0.16), transparent 24rem),
+ var(--optema-cloud);
+}
+
+h1, h2, h3, .navbar-brand {
+ font-family: 'Plus Jakarta Sans', 'Inter', sans-serif;
+ letter-spacing: -0.04em;
+}
+
+a { color: inherit; }
+a:focus-visible, button:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-visible {
+ outline: 3px solid rgba(246, 174, 45, 0.65) !important;
+ outline-offset: 3px;
+}
+
+.optema-nav {
+ position: sticky;
+ top: 0;
+ z-index: 20;
+ background: rgba(247, 250, 252, 0.82);
+ backdrop-filter: blur(18px);
+ border-bottom: 1px solid rgba(220, 232, 238, 0.82);
+}
+.brand-mark { font-weight: 800; color: var(--optema-ink); }
+.brand-icon {
+ display: inline-grid;
+ place-items: center;
+ width: 2.15rem; height: 2.15rem;
+ margin-right: .45rem;
+ border-radius: 14px;
+ color: white;
+ background: linear-gradient(135deg, var(--optema-primary), var(--optema-accent));
+ box-shadow: 0 12px 24px rgba(15, 139, 141, 0.24);
+}
+.nav-link { font-weight: 700; color: var(--optema-muted); }
+.nav-link:hover, .nav-link:focus { color: var(--optema-primary); }
+.admin-link {
+ border: 1px solid var(--optema-line);
+ border-radius: 999px;
+ padding-inline: 1rem !important;
+ background: rgba(255,255,255,.7);
+}
+
+.hero-section {
+ min-height: 78vh;
+ display: flex;
+ align-items: center;
+ background:
+ linear-gradient(145deg, rgba(223,248,245,.88), rgba(255,246,228,.72) 55%, rgba(255,255,255,.9)),
+ var(--optema-cloud);
+}
+.shape {
+ position: absolute; border-radius: 999px; filter: blur(.2px); opacity: .82;
+}
+.shape-one {
+ width: 15rem; height: 15rem; right: 6%; top: 12%;
+ background: linear-gradient(135deg, rgba(15,139,141,.2), rgba(246,174,45,.38));
+}
+.shape-two {
+ width: 9rem; height: 9rem; left: 4%; bottom: 8%;
+ background: linear-gradient(135deg, rgba(242,100,25,.22), rgba(15,139,141,.2));
+}
+.eyebrow, .section-kicker {
+ color: var(--optema-primary-dark);
+ text-transform: uppercase;
+ letter-spacing: .12em;
+ font-size: .78rem;
+ font-weight: 800;
+}
+.display-heading {
+ font-size: clamp(2.7rem, 6vw, 5.9rem);
+ line-height: .95;
+ font-weight: 800;
+}
+.hero-copy, .section-copy {
+ color: var(--optema-muted);
+ font-size: clamp(1rem, 1vw + .76rem, 1.2rem);
+ line-height: 1.75;
+}
+.btn-optema {
+ border: 0;
+ color: #fff;
+ font-weight: 800;
+ border-radius: 999px;
+ padding: .9rem 1.35rem;
+ background: linear-gradient(135deg, var(--optema-primary), var(--optema-primary-dark));
+ box-shadow: 0 14px 35px rgba(15, 139, 141, .26);
+}
+.btn-optema:hover, .btn-optema:focus { color:#fff; transform: translateY(-1px); box-shadow: 0 18px 44px rgba(15,139,141,.32); }
+.btn-outline-optema {
+ border: 1px solid rgba(15,139,141,.3);
+ color: var(--optema-primary-dark);
+ background: rgba(255,255,255,.68);
+ font-weight: 800;
+ border-radius: 999px;
+ padding: .9rem 1.35rem;
+}
+.btn-outline-optema:hover, .btn-outline-optema:focus { background: var(--optema-mint); color: var(--optema-primary-dark); }
+.trust-strip { display: flex; flex-wrap: wrap; gap: .65rem; }
+.trust-strip span, .score-pill, .status-dot {
+ display: inline-flex; align-items: center; gap: .45rem;
+ border: 1px solid rgba(15,139,141,.16);
+ background: rgba(255,255,255,.72);
+ border-radius: 999px;
+ padding: .55rem .8rem;
+ color: var(--optema-muted);
+ font-weight: 800;
+ font-size: .86rem;
+}
+.status-dot i { width: .55rem; height: .55rem; border-radius: 999px; background: #23c483; box-shadow: 0 0 0 6px rgba(35,196,131,.15); }
+.glass-card, .form-shell, .analysis-card, .empty-state, .case-card, .metric-card {
+ border: 1px solid rgba(220, 232, 238, .9);
+ background: rgba(255,255,255,.82);
+ backdrop-filter: blur(20px);
+ box-shadow: var(--optema-shadow);
+ border-radius: 32px;
+}
+.decision-panel { padding: clamp(1.35rem, 3vw, 2rem); }
+.mini-chart { display: grid; gap: 1rem; }
+.mini-chart div {
+ --bar: 70%;
+ position: relative; overflow: hidden;
+ display: flex; justify-content: space-between;
+ padding: .9rem 1rem;
+ border-radius: 18px;
+ background: #eef6f7;
+ font-weight: 800;
+}
+.mini-chart div::before {
+ content: ''; position: absolute; inset: 0 auto 0 0; width: var(--bar);
+ background: linear-gradient(90deg, rgba(15,139,141,.24), rgba(246,174,45,.28));
+}
+.mini-chart span, .mini-chart b { position: relative; z-index: 1; }
+.bar-95 { --bar: 95% !important; } .bar-88 { --bar: 88% !important; } .bar-82 { --bar: 82% !important; }
+.solution-preview {
+ padding: 1.2rem; border-radius: 24px;
+ background: linear-gradient(135deg, var(--optema-ink), #183b56); color: white;
+}
+.solution-preview h2 { font-size: 1.35rem; }
+.solution-preview p { color: rgba(255,255,255,.74); margin-bottom: 0; }
+.small-label { color: var(--optema-secondary) !important; text-transform: uppercase; font-weight: 800; letter-spacing: .12em; font-size: .75rem; }
+
+.section-pad { padding: clamp(4rem, 8vw, 7rem) 0; }
+.section-title, .page-title { font-size: clamp(2rem, 3.5vw, 3.6rem); font-weight: 800; line-height: 1.02; }
+.form-shell { padding: clamp(1.25rem, 3vw, 2rem); }
+.form-control, .form-select { border: 1px solid var(--optema-line); border-radius: 18px; padding: .9rem 1rem; }
+.form-control:focus, .form-select:focus { border-color: var(--optema-primary); box-shadow: 0 0 0 .22rem rgba(15,139,141,.13); }
+.form-label { font-weight: 800; color: var(--optema-ink); }
+.form-text { color: var(--optema-muted); }
+.feature-list { display: grid; gap: .85rem; margin-top: 1.5rem; }
+.feature-list div { display: flex; gap: .8rem; align-items: center; color: var(--optema-muted); }
+.feature-list strong {
+ display:grid; place-items:center; width:2rem; height:2rem; border-radius:12px;
+ background: var(--optema-sand); color: var(--optema-accent);
+}
+.case-card {
+ display: flex; flex-direction: column; gap: .9rem;
+ padding: 1.35rem; text-decoration: none; transition: .2s ease;
+}
+.case-card:hover, .case-card:focus { transform: translateY(-4px); border-color: rgba(15,139,141,.35); color: var(--optema-ink); }
+.case-card h2, .case-card h3 { font-size: 1.15rem; font-weight: 800; margin: 0; }
+.case-card p { color: var(--optema-muted); margin: 0; line-height: 1.65; }
+.case-area { color: var(--optema-primary-dark); font-size: .78rem; font-weight: 900; text-transform: uppercase; letter-spacing: .1em; }
+.case-meta { display: flex; flex-wrap: wrap; justify-content: space-between; gap: .7rem; color: var(--optema-muted); font-weight: 800; font-size: .86rem; margin-top: auto; }
+.empty-state { padding: 2rem; text-align: center; }
+.empty-state p { color: var(--optema-muted); }
+.link-optema { color: var(--optema-primary-dark); font-weight: 900; text-decoration: none; }
+.link-optema:hover { color: var(--optema-accent); }
+.site-footer {
+ padding: 1.5rem 0; color: var(--optema-muted);
+ border-top: 1px solid var(--optema-line); background: rgba(255,255,255,.68);
+}
+.page-shell { min-height: 100vh; background: linear-gradient(180deg, rgba(223,248,245,.55), transparent 24rem); }
+.detail-hero { padding: clamp(3rem, 6vw, 5rem) 0; background: linear-gradient(135deg, rgba(223,248,245,.9), rgba(255,246,228,.85)); }
+.metric-grid { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 1rem; }
+.metric-card { padding: 1.25rem; }
+.metric-card span { color: var(--optema-muted); font-weight: 800; display:block; }
+.metric-card strong { font-family:'Plus Jakarta Sans'; font-size: 1.75rem; }
+.analysis-card { padding: clamp(1.2rem, 2.6vw, 2rem); }
+.analysis-card h2 { font-weight: 800; margin-bottom: 1.2rem; }
+.cause-list { display: grid; gap: 1.2rem; }
+.cause-list article p { color: var(--optema-muted); margin: .6rem 0 0; line-height: 1.6; }
+.progress { height: .65rem; background: #e6f2f3; border-radius: 999px; margin-top: .55rem; }
+.progress-bar { background: linear-gradient(90deg, var(--optema-primary), var(--optema-secondary)); }
+.solution-table { --bs-table-bg: transparent; }
+.solution-table th { color: var(--optema-muted); text-transform: uppercase; letter-spacing: .08em; font-size: .78rem; }
+.solution-table td { padding: 1rem .75rem; }
+.solution-table p { color: var(--optema-muted); margin: .25rem 0; }
+.solution-table small { color: var(--optema-primary-dark); font-weight: 800; }
+.rank-badge { display: inline-flex; border-radius: 999px; background: var(--optema-sand); color: var(--optema-accent); padding: .35rem .6rem; font-weight: 900; }
+.action-card { background: linear-gradient(145deg, rgba(255,255,255,.92), rgba(223,248,245,.76)); }
+.timeline { display: grid; gap: 1rem; }
+.timeline article { display: grid; grid-template-columns: 5rem 1fr; gap: 1rem; align-items: start; }
+.timeline article > span {
+ display: inline-flex; justify-content:center; padding: .45rem .65rem; border-radius: 999px;
+ background: var(--optema-ink); color: white; font-weight: 900; font-size: .82rem;
+}
+.timeline h3 { font-size: 1.05rem; font-weight: 800; margin-bottom: .2rem; }
+.timeline p { color: var(--optema-muted); margin: 0; line-height: 1.6; }
+
+.score-width-0 { width: 0%; }
+.score-width-1 { width: 1%; }
+.score-width-2 { width: 2%; }
+.score-width-3 { width: 3%; }
+.score-width-4 { width: 4%; }
+.score-width-5 { width: 5%; }
+.score-width-6 { width: 6%; }
+.score-width-7 { width: 7%; }
+.score-width-8 { width: 8%; }
+.score-width-9 { width: 9%; }
+.score-width-10 { width: 10%; }
+.score-width-11 { width: 11%; }
+.score-width-12 { width: 12%; }
+.score-width-13 { width: 13%; }
+.score-width-14 { width: 14%; }
+.score-width-15 { width: 15%; }
+.score-width-16 { width: 16%; }
+.score-width-17 { width: 17%; }
+.score-width-18 { width: 18%; }
+.score-width-19 { width: 19%; }
+.score-width-20 { width: 20%; }
+.score-width-21 { width: 21%; }
+.score-width-22 { width: 22%; }
+.score-width-23 { width: 23%; }
+.score-width-24 { width: 24%; }
+.score-width-25 { width: 25%; }
+.score-width-26 { width: 26%; }
+.score-width-27 { width: 27%; }
+.score-width-28 { width: 28%; }
+.score-width-29 { width: 29%; }
+.score-width-30 { width: 30%; }
+.score-width-31 { width: 31%; }
+.score-width-32 { width: 32%; }
+.score-width-33 { width: 33%; }
+.score-width-34 { width: 34%; }
+.score-width-35 { width: 35%; }
+.score-width-36 { width: 36%; }
+.score-width-37 { width: 37%; }
+.score-width-38 { width: 38%; }
+.score-width-39 { width: 39%; }
+.score-width-40 { width: 40%; }
+.score-width-41 { width: 41%; }
+.score-width-42 { width: 42%; }
+.score-width-43 { width: 43%; }
+.score-width-44 { width: 44%; }
+.score-width-45 { width: 45%; }
+.score-width-46 { width: 46%; }
+.score-width-47 { width: 47%; }
+.score-width-48 { width: 48%; }
+.score-width-49 { width: 49%; }
+.score-width-50 { width: 50%; }
+.score-width-51 { width: 51%; }
+.score-width-52 { width: 52%; }
+.score-width-53 { width: 53%; }
+.score-width-54 { width: 54%; }
+.score-width-55 { width: 55%; }
+.score-width-56 { width: 56%; }
+.score-width-57 { width: 57%; }
+.score-width-58 { width: 58%; }
+.score-width-59 { width: 59%; }
+.score-width-60 { width: 60%; }
+.score-width-61 { width: 61%; }
+.score-width-62 { width: 62%; }
+.score-width-63 { width: 63%; }
+.score-width-64 { width: 64%; }
+.score-width-65 { width: 65%; }
+.score-width-66 { width: 66%; }
+.score-width-67 { width: 67%; }
+.score-width-68 { width: 68%; }
+.score-width-69 { width: 69%; }
+.score-width-70 { width: 70%; }
+.score-width-71 { width: 71%; }
+.score-width-72 { width: 72%; }
+.score-width-73 { width: 73%; }
+.score-width-74 { width: 74%; }
+.score-width-75 { width: 75%; }
+.score-width-76 { width: 76%; }
+.score-width-77 { width: 77%; }
+.score-width-78 { width: 78%; }
+.score-width-79 { width: 79%; }
+.score-width-80 { width: 80%; }
+.score-width-81 { width: 81%; }
+.score-width-82 { width: 82%; }
+.score-width-83 { width: 83%; }
+.score-width-84 { width: 84%; }
+.score-width-85 { width: 85%; }
+.score-width-86 { width: 86%; }
+.score-width-87 { width: 87%; }
+.score-width-88 { width: 88%; }
+.score-width-89 { width: 89%; }
+.score-width-90 { width: 90%; }
+.score-width-91 { width: 91%; }
+.score-width-92 { width: 92%; }
+.score-width-93 { width: 93%; }
+.score-width-94 { width: 94%; }
+.score-width-95 { width: 95%; }
+.score-width-96 { width: 96%; }
+.score-width-97 { width: 97%; }
+.score-width-98 { width: 98%; }
+.score-width-99 { width: 99%; }
+.score-width-100 { width: 100%; }
+
+@media (max-width: 767px) {
+ .metric-grid { grid-template-columns: 1fr; }
+ .timeline article { grid-template-columns: 1fr; }
+ .hero-section { min-height: auto; }
+}
+
+
+/* Streamlit-inspired MVP workspace */
+.hero-subtitle {
+ font-family: 'Plus Jakarta Sans', 'Inter', sans-serif;
+ font-size: clamp(1.25rem, 1.2vw + 1rem, 2rem);
+ font-weight: 800;
+ color: var(--optema-primary-dark);
+ letter-spacing: -0.03em;
+}
+.mvp-hero .display-heading { letter-spacing: -0.07em; }
+.mvp-workspace .sticky-lg-top { top: 6rem; }
+.panel-heading {
+ display: flex;
+ gap: 1rem;
+ align-items: flex-start;
+ margin-bottom: 1.4rem;
+}
+.panel-heading h2 {
+ font-size: 1.45rem;
+ margin: 0 0 .25rem;
+ font-weight: 900;
+}
+.panel-heading p,
+.form-note,
+.formula-note,
+.detail-status-card p {
+ color: var(--optema-muted);
+ line-height: 1.65;
+}
+.panel-icon {
+ display: inline-grid;
+ place-items: center;
+ width: 3rem;
+ height: 3rem;
+ border-radius: 18px;
+ background: linear-gradient(135deg, var(--optema-mint), var(--optema-sand));
+ box-shadow: 0 16px 28px rgba(15, 139, 141, .12);
+ font-size: 1.35rem;
+}
+.problem-textarea {
+ min-height: 15rem;
+ resize: vertical;
+ border-radius: 22px;
+ border-color: rgba(15,139,141,.18);
+ background: rgba(255,255,255,.82);
+}
+.problem-textarea:focus {
+ border-color: var(--optema-primary);
+ box-shadow: 0 0 0 .25rem rgba(15,139,141,.12);
+}
+.urgency-slider { accent-color: var(--optema-primary); }
+.urgency-value {
+ min-width: 2.4rem;
+ text-align: center;
+ padding: .35rem .7rem;
+ border-radius: 999px;
+ color: #fff;
+ background: var(--optema-ink);
+ font-weight: 900;
+}
+.range-labels {
+ display: flex;
+ justify-content: space-between;
+ color: var(--optema-muted);
+ font-size: .82rem;
+ font-weight: 800;
+ margin-top: -.3rem;
+}
+.output-panel { overflow: hidden; }
+.output-success {
+ display: inline-flex;
+ align-items: center;
+ gap: .5rem;
+ color: #087447;
+ background: rgba(35, 196, 131, .12);
+ border: 1px solid rgba(35, 196, 131, .22);
+ border-radius: 999px;
+ padding: .7rem 1rem;
+ font-weight: 900;
+}
+.output-section {
+ padding: 1.25rem 0;
+ border-top: 1px solid var(--optema-line);
+}
+.output-section:first-of-type { border-top: 0; padding-top: 0; }
+.output-section h2 { font-size: clamp(1.25rem, 1vw + 1rem, 1.8rem); }
+.compact-cause-list { gap: .85rem; }
+.compact-cause-list article p { display: none; }
+.preview-table th,
+.preview-table td { white-space: nowrap; }
+.detail-status-card { padding: 1.35rem; }
+.bar-85 { --bar: 85% !important; }
+.bar-70 { --bar: 70% !important; }
+
+@media (max-width: 991px) {
+ .mvp-workspace .sticky-lg-top { position: static; }
}