// 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) => (
{g.label}
{g.sub}
))}
{/* 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 });