// rating-form.jsx — Mobile-optimized version of the "Deine Meinung" rating form.
// Original desktop is a wide table with criteria rows × ++/+/?/−/−− columns.
// Mobile reflow: each criterion is a row with a 5-segment selector underneath the label.
// Sections are collapsible accordions; users can skip what they're unsure about.
function RatingForm({ dark }) {
const t = useTheme(dark);
// Each criterion's `v` is 0..4 mapping to [++, +, ?, −, −−], or null = unanswered
const sections = [
{
id: 'general', label: 'General', open: false,
criteria: [
{ id: 'g1', label: 'Worthwhile investment > 10% / year', v: 0 },
{ id: 'g2', label: 'Market leader or top 3', v: 1 },
{ id: 'g3', label: 'Future-proof business model', v: 0 },
{ id: 'g4', label: 'Strong brand', v: null },
],
},
{
id: 'financials', label: 'Financials', open: true,
criteria: [
{ id: 'f1', label: 'Revenue growth', v: 1 },
{ id: 'f2', label: 'EBIT growth', v: 1 },
{ id: 'f3', label: 'EBIT margin', v: 2 },
{ id: 'f4', label: 'Valuation', v: 3 },
{ id: 'f5', label: 'Dividend yield', v: 2 },
{ id: 'f6', label: 'Cash flow', v: null },
{ id: 'f7', label: 'Credit rating', v: 0 },
{ id: 'f8', label: 'Growth investments', v: 0 },
{ id: 'f9', label: 'Balance sheet risks', v: null },
],
},
{ id: 'business', label: 'Business model', open: false,
criteria: [
{ id: 'b1', label: 'Pricing power', v: null },
{ id: 'b2', label: 'Moat / network effects', v: null },
{ id: 'b3', label: 'Recurring revenue', v: null },
]},
{ id: 'sustain', label: 'Sustainability', open: false,
criteria: [
{ id: 's1', label: 'Sustainability matters', v: 3 },
{ id: 's2', label: 'Governance quality', v: null },
]},
{ id: 'risk', label: 'Risk factors', open: false,
criteria: [
{ id: 'r1', label: 'Regulatory exposure', v: null },
{ id: 'r2', label: 'Concentration risk', v: null },
]},
];
// Totals for progress strip
let answered = 0, total = 0;
sections.forEach(s => { (s.criteria || []).forEach(c => { total++; if (c.v != null) answered++; }); });
return (
{/* Header */}
×
Rate AMZN
What speaks for or against · next 2–3 years
Skip
{/* Progress strip */}
{answered}
of
{total}
answered
Skip any you're unsure about
{Array.from({ length: total }).map((_, i) => (
))}
{/* Legend (the 5 grades) */}
{[
{ label: '++', sub: 'Strong pro', fg: t.up, bg: t.upSoft },
{ label: '+', sub: 'Pro', fg: t.up, bg: t.upSoft },
{ label: '?', sub: 'Unsure', fg: t.textMuted, bg: t.chip },
{ label: '−', sub: 'Con', fg: t.down, bg: t.downSoft },
{ label: '−−', sub: 'Strong con', fg: t.down, bg: t.downSoft },
].map((g, i) => (
))}
{/* Sections */}
{sections.map(s => )}
{/* Sticky save bar */}
Save rating
{answered}/{total}
);
}
// ───────────────────────── Section accordion ─────────────────────────
function RatingSection({ t, section }) {
const s = section;
const ans = (s.criteria || []).filter(c => c.v != null).length;
const tot = (s.criteria || []).length;
return (
{/* Section header */}
{s.label}
{ans} of {tot} answered
{/* Section body */}
{s.open && (
{s.criteria.map((c, i) => (
))}
)}
);
}
// ───────────────────────── One criterion row ─────────────────────────
function CriterionRow({ t, criterion, first }) {
const c = criterion;
// Color spectrum per grade (idx 0..4 = ++, +, ?, −, −−)
const grades = [
{ label: '++', sel: t.up, soft: t.upSoft, hint: 'rgba(0,184,92,0.04)', fg: t.up },
{ label: '+', sel: t.up, soft: t.upSoft, hint: 'rgba(0,184,92,0.04)', fg: t.up },
{ label: '?', sel: t.textMuted, soft: t.chip, hint: t.chip, fg: t.textMuted },
{ label: '−', sel: t.down, soft: t.downSoft, hint: 'rgba(229,52,42,0.04)', fg: t.down },
{ label: '−−', sel: t.down, soft: t.downSoft, hint: 'rgba(229,52,42,0.04)', fg: t.down },
];
// For dark mode, hint tints need adjustment
if (t.name === 'dark') {
grades[0].hint = 'rgba(0,210,106,0.06)';
grades[1].hint = 'rgba(0,210,106,0.06)';
grades[3].hint = 'rgba(255,69,58,0.06)';
grades[4].hint = 'rgba(255,69,58,0.06)';
}
return (
{c.label}
{grades.map((g, i) => {
const active = c.v === i;
// Faded — when SOMETHING is selected and this isn't it
const hasOther = c.v != null && !active;
return (
{g.label}
);
})}
);
}
Object.assign(window, { RatingForm });