import '../about/internal/application/EscalationTier.dart'; import '../about/internal/application/Standing.dart'; import '../about/internal/application/ToneLevel.dart'; /// All user-facing enforcement copy comes from here, so the hard cap is /// enforceable in one place: language may criticise behaviour, never the /// person. Nothing that mocks the user's worth ships. class ToneEngine { /// The escalation ladder. Disappointment is deliberately placed above anger /// because it works better as a lever. static EscalationTier tierFor(int unacknowledgedCount) { if (unacknowledgedCount <= 0) { return EscalationTier.Reminder; } if (unacknowledgedCount == 1) { return EscalationTier.Nudge; } if (unacknowledgedCount == 2) { return EscalationTier.Nag; } if (unacknowledgedCount == 3) { return EscalationTier.Disappointed; } return EscalationTier.Cold; } /// Notification body for a commitment at a given tier and tone. static String nudge( EscalationTier tier, ToneLevel tone, String title, ) { switch (tier) { case EscalationTier.Reminder: return "$title is due now."; case EscalationTier.Nudge: return "$title is still sitting there."; case EscalationTier.Nag: return tone == ToneLevel.Firm ? "$title is overdue. It needs a decision." : "Third time: $title. Do it or abandon it."; case EscalationTier.Disappointed: return "You said you would do $title. You have not."; case EscalationTier.Cold: return "$title. No more reminders about this one."; } } /// The header copy on app open, which shifts with standing. static String standingHeadline(Standing standing, ToneLevel tone) { switch (standing) { case Standing.Good: return "You are in good standing."; case Standing.Warned: return "You are slipping."; case Standing.Grounded: return tone == ToneLevel.Firm ? "You are grounded until this is cleared." : "Grounded. Nothing new until the queue is empty."; case Standing.Lockdown: return "Lockdown. One item at a time."; } } static String standingBody(Standing standing, ToneLevel tone) { switch (standing) { case Standing.Good: return "Keep the plan honest and it stays this way."; case Standing.Warned: return "New electives are blocked. Clear some debt before adding more."; case Standing.Grounded: return "Your plans are hidden. This is what is actually outstanding."; case Standing.Lockdown: return "Complete or abandon each item below. There is no third option."; } } /// Praise is rationed but real. A parent who only criticises gets tuned out — /// this returns empty unless something specific was genuinely earned. static String praise(String specificAchievement) { if (specificAchievement.isEmpty) { return ""; } return specificAchievement; } /// The register the app switches to when distress is detected. The strict /// persona drops entirely — this is the difference between a product people /// keep and one they resent. static String distressHeadline() { return "Let us cut this back."; } static String distressBody() { return "Something has clearly been hard lately. Pick three things that " "genuinely matter this week and let the rest go. Nothing here is " "counting against you right now."; } /// Copy shown when a deferral is refused because the cap is spent. static String deferralRefused(int maxDeferrals) { return "This has been deferred $maxDeferrals times. It can now only be " "completed or abandoned."; } /// Copy shown when a non-negotiable deferral is attempted. static String nonNegotiableRefused() { return "Non-negotiables are not deferrable. That is what makes them " "non-negotiable."; } /// Copy shown when the excuse is too short. static String excuseTooShort(int minimum) { return "Write at least $minimum characters. If it is not worth explaining, " "it is not worth deferring."; } }