HEX
Server: LiteSpeed
System: Linux houston.panomity.com 6.8.0-100-generic #100-Ubuntu SMP PREEMPT_DYNAMIC Tue Jan 13 16:40:06 UTC 2026 x86_64
User: nudepix (1011)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: //proc/self/root/tmp/contextual_rate_page.mjs
import fs from 'node:fs';
const file = 'web/src/modules/photos/RatePage.jsx';
let source = fs.readFileSync(file, 'utf8');

if (!source.includes('const getRateContext =')) {
  const helper = `
const getRateContext = (category = 'all') => {
	const key = String(category || 'all').toLowerCase();
	if (['dating', 'tinder', 'bumble'].includes(key)) {
		return {
			key: 'dating',
			label: 'Dating-Foto',
			heading: 'Wie wirkt dieses Foto im Dating-Kontext?',
			subtitle: 'Bewerte die Foto-Wirkung: offen, sympathisch und natürlich. Es geht nicht um die Person als Ganzes.',
			lowLabel: 'wirkt schwach',
			highLabel: 'wirkt sehr stark',
			chipA: 'Sympathie',
			chipB: 'Natürlichkeit',
			chipC: 'erste Einladung',
			uploadEntry: 'rate_context_dating'
		};
	}
	if (['business', 'linkedin', 'bewerbung', 'kompetenz'].includes(key)) {
		return {
			key: 'business',
			label: 'Business-Foto',
			heading: 'Wie wirkt dieses Foto beruflich?',
			subtitle: 'Bewerte Kompetenz, Vertrauen und Nahbarkeit. Ein gutes Business-Foto wirkt professionell, aber nicht distanziert.',
			lowLabel: 'wenig überzeugend',
			highLabel: 'sehr überzeugend',
			chipA: 'Kompetenz',
			chipB: 'Vertrauen',
			chipC: 'Nahbarkeit',
			uploadEntry: 'rate_context_business'
		};
	}
	if (['social', 'instagram', 'facebook', 'whatsapp', 'selfie'].includes(key)) {
		return {
			key: 'social',
			label: 'Social-Profilbild',
			heading: 'Wie wirkt dieses Foto als Social-Profilbild?',
			subtitle: 'Bewerte Wiedererkennung, Sympathie und ob das Bild auch klein als Avatar funktioniert.',
			lowLabel: 'geht unter',
			highLabel: 'funktioniert stark',
			chipA: 'Avatar-Wirkung',
			chipB: 'Sympathie',
			chipC: 'Klarheit',
			uploadEntry: 'rate_context_social'
		};
	}
	return {
		key: 'all',
		label: 'Foto-Wirkung',
		heading: 'Welche erste Wirkung hat dieses Foto?',
		subtitle: 'Bewerte das Foto respektvoll nach Wirkung, Klarheit und Passung. Es geht um Entscheidungshilfe, nicht um Beschämung.',
		lowLabel: 'passt noch nicht',
		highLabel: 'wirkt sehr stark',
		chipA: 'Klarheit',
		chipB: 'Sympathie',
		chipC: 'Passung',
		uploadEntry: 'rate_context_all'
	};
};
`;
  source = source.replace('\nexport const RatePage = () => {', `${helper}\nexport const RatePage = () => {`);
}

if (!source.includes('const rateContext = getRateContext(category);')) {
  source = source.replace(
    "\tconst canNativeShare = typeof navigator !== 'undefined' && typeof navigator.share === 'function';",
    "\tconst canNativeShare = typeof navigator !== 'undefined' && typeof navigator.share === 'function';\n\tconst rateContext = getRateContext(category);"
  );
}

source = source.replace(
  `\t\t\t\ttext: 'Gib diesem Foto bitte ehrliches Feedback auf bewertemich.de.',`,
  `\t\t\t\ttext: rateContext.heading + ' ' + rateContext.subtitle,`
);

source = source.replace(
  `\t\tvoid trackEvent('vote_streak_progress', {\n\t\t\tphotoId: Number(id),\n\t\t\tsource: inviteAttribution.isInvite ? inviteAttribution.source : 'community',\n\t\t\tsurface: 'rate_page',\n\t\t\tentryPoint: category,\n\t\t\tmeta: { streak: next, score }\n\t\t});`,
  `\t\tvoid trackEvent('vote_streak_progress', {\n\t\t\tphotoId: Number(id),\n\t\t\tsource: inviteAttribution.isInvite ? inviteAttribution.source : 'community',\n\t\t\tsurface: 'rate_page',\n\t\t\tentryPoint: category,\n\t\t\tmeta: { streak: next, score, context: rateContext.key }\n\t\t});`
);

source = source.replace(
  `\t\t\t{showVoteToUpload ? (`,
  `\t\t\t<section className="mb-5 rounded-3xl border border-slate-200 bg-white p-5 shadow-sm">\n\t\t\t\t<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">\n\t\t\t\t\t<div>\n\t\t\t\t\t\t<div className="text-xs font-bold uppercase tracking-[0.22em] text-coral-600">{rateContext.label}</div>\n\t\t\t\t\t\t<h1 className="mt-2 text-2xl font-black text-slate-950">{rateContext.heading}</h1>\n\t\t\t\t\t\t<p className="mt-2 max-w-2xl text-sm leading-6 text-slate-600">{rateContext.subtitle}</p>\n\t\t\t\t\t</div>\n\t\t\t\t\t<Link\n\t\t\t\t\t\tto={buildUploadPath(rateContext.uploadEntry)}\n\t\t\t\t\t\tclassName="btn border-0 bg-slate-950 text-white hover:bg-slate-800"\n\t\t\t\t\t\tonClick={() => trackEvent('rate_context_upload_click', { source: 'rate_page', surface: 'context_card', entryPoint: rateContext.key })}\n\t\t\t\t\t>\n\t\t\t\t\t\tEigenes Foto in diesem Kontext testen\n\t\t\t\t\t</Link>\n\t\t\t\t</div>\n\t\t\t\t<div className="mt-4 flex flex-wrap gap-2 text-sm">\n\t\t\t\t\t<span className="rounded-full bg-slate-100 px-3 py-1 text-slate-700">{rateContext.chipA}</span>\n\t\t\t\t\t<span className="rounded-full bg-slate-100 px-3 py-1 text-slate-700">{rateContext.chipB}</span>\n\t\t\t\t\t<span className="rounded-full bg-slate-100 px-3 py-1 text-slate-700">{rateContext.chipC}</span>\n\t\t\t\t</div>\n\t\t\t</section>\n\t\t\t{showVoteToUpload ? (`
);

source = source.replace(
  `\t\t\t\t<div className="text-gray-700">Ø {stats.avg_score ? Number(stats.avg_score).toFixed(1) : '-'} · {stats.votes} Stimmen</div>\n\t\t\t\t<div className="flex flex-wrap gap-2">`,
  `\t\t\t\t<div>\n\t\t\t\t\t<div className="text-gray-700">Ø {stats.avg_score ? Number(stats.avg_score).toFixed(1) : '-'} · {stats.votes} Stimmen</div>\n\t\t\t\t\t<div className="mt-1 text-xs text-gray-500">1 = {rateContext.lowLabel} · 10 = {rateContext.highLabel}</div>\n\t\t\t\t</div>\n\t\t\t\t<div className="flex flex-wrap gap-2" aria-label={rateContext.heading}>`
);

source = source.replace(
  `\t\t\t\t\t\t<button key={n} className="btn btn-ghost border" onClick={() => vote(n)} disabled={hasVoted}>{n}</button>`,
  `\t\t\t\t\t\t<button key={n} className="btn btn-ghost border" aria-label={rateContext.heading + ' Bewertung ' + n + ' von 10'} onClick={() => vote(n)} disabled={hasVoted}>{n}</button>`
);

source = source.replace(
  `to={buildUploadPath('vote_streak_prompt')}`,
  `to={buildUploadPath('vote_streak_prompt_' + rateContext.key)}`
);

fs.writeFileSync(file, source, 'utf8');
console.log('contextual rate page added');