Files
LlamaCasty/ViewModels/MainViewModel.OutputProfile.cs
T
gramps 0de953ef66 feat(about): rebuild the About box as a conversion surface
Creator: the About box was "shitty" — an unreadable logo, a version number nobody asked for, copy that
insulted the creator, and buttons that looked like text. Rebuilt around one job: convert.

- **820×860 modal** (was 650×700) with a scrollable hub, and the logo at **520 px wide** instead of
  125 — the asset is 2816×1536, so it was being drawn as a thumbnail, not an icon problem.
- **Pre-Release pill** replaces "Version 1.0.0" (the assembly version stays in log lines). The pill is a
  button: it opens llamacasty.com until a Steam store page exists, so the link changes in one place.
- Tagline: "1-2-3, and go live to the world on YouTube!" The old "simple enough that even the most
  right-brained person can intuit it" is gone, and a test asserts it stays gone.
- The offer in three short blocks (free 30 fps · paid 60 fps · one-time, lifetime, no account /
  subscription / tracking), then the launch price and **one primary CTA**: "Unlock 60 fps — $79".
- Price: $79 until **April 1, 2027**, then $99 locked for life. The numbers live once, in
  `MainViewModel.Overlays`, and are recorded in `docs/ai/commercial.md`.
- **A paying customer is never sold to**: ownership collapses the price/CTA block and shows
  "Licence: owned via Steam — thank you."
- Secondary destinations (channel, Mastodon, coffee, licences) are one quiet row; footer is
  `© Llama Chile Shop`; the "Made by gramps" line is removed.
- `YtButtonSecondary` now carries a higher surface, a hairline border and a keyboard focus ring — they
  were the same tone as the panel behind them, which is why they read as plain text.

Test: `AboutHubTests.About_ShowsTheOfferToFreeUsers_AndNothingToSellToOwners` — tagline, pill, price,
CTA text, copyright, the removed lines stay removed, and the CTA block collapses when owned.
FAST gate: 540 passed, 0 failed, 0 warnings.
2026-11-05 13:00:57 -08:00

87 lines
2.7 KiB
C#

using ytLive.Helpers;
using ytLive.Models;
using ytLive.Services.Entitlement;
namespace ytLive.ViewModels;
/// <summary>
/// The app-wide Steam ownership flag and its output-profile consumer.
///
/// <para>Only the entitlement service decides whether the current Steam account owns the paid app.
/// Development builds use a fake owned result; Release builds use Steam.</para>
/// </summary>
public partial class MainViewModel
{
private IEntitlement _entitlement = CreateEntitlement();
private bool _isAppOwned;
private string? _entitlementErrorMessage;
public bool IsAppOwned
{
get => _isAppOwned;
private set
{
var changed = SetProperty(ref _isAppOwned, value);
EntitlementErrorMessage = _entitlement.ErrorMessage;
if (!changed) return;
OnPropertyChanged(nameof(HasProOutput));
OnPropertyChanged(nameof(LicenceStateLabel));
RefreshSteamLicenceAlert();
if (!HasProOutput && _selectedQuality is { RequiresPro: true } && CanChangeQuality)
SelectedQuality = QualityOptions.Single(option => option.Display == OutputProfile.FreeFallbackName);
}
}
public string? EntitlementErrorMessage
{
get => _entitlementErrorMessage;
private set => SetProperty(ref _entitlementErrorMessage, value);
}
public bool HasProOutput => DeveloperMode || IsAppOwned;
private static IEntitlement CreateEntitlement()
{
#if DEBUG
return new FakeEntitlement(true);
#else
return SteamEntitlement.NotConfigured();
#endif
}
/// <summary>Test seam: swap the ownership source without a Steam client.</summary>
internal IEntitlement EntitlementOverride
{
get => _entitlement;
set
{
_entitlement = value;
IsAppOwned = value.IsAppOwned;
EntitlementErrorMessage = value.ErrorMessage;
}
}
private bool _developerMode;
/// <summary>
/// Development-only override: ignore the output budget and allow every shipped tier.
///
/// <para>There is no UI to reach this in a Release build — the panel that hosts the
/// checkbox is excluded from the Release compile — and the setter refuses the value
/// unless <see cref="DevBuild.IsDevelopment"/> is true, so even a hand-edited settings
/// file cannot unlock it.</para>
/// </summary>
public bool DeveloperMode
{
get => _developerMode;
set
{
if (!DevBuild.IsDevelopment) return;
if (!SetProperty(ref _developerMode, value)) return;
OnPropertyChanged(nameof(HasProOutput));
OnPropertyChanged(nameof(LicenceStateLabel));
RefreshSteamLicenceAlert();
}
}
}