+ Back to the Concept Mill
+ @Model.Subject.Title
+
+
+ - Everyone
+ - @Model.Subject.Tally.Up up, @Model.Subject.Tally.Down down
+ - Members
+ - @Model.Subject.Tally.Members.Up up, @Model.Subject.Tally.Members.Down down
+
+
+
+ @if (stance is null)
+ {
+ You have not voted on this concept.
+ }
+ else
+ {
+ Your vote: @(stance == Bifrost.Web.Domain.ConceptStance.Up ? "up" : "down").
+ }
+
+
+
+
+
+ @if (stance is not null)
+ {
+
+ }
+
+
+ @if (Model.SignedIn)
+ {
+ Your vote is kept against your Bifrost account. No cookie is set.
+ }
+ else
+ {
+
+ Voting saves one small cookie on this device. Its only purpose is to keep your one vote yours so you can
+ change or retract it; it lasts a year and carries nothing else.
+
+ }
+
diff --git a/src/Bifrost.Web/Pages/Mill/Vote.cshtml.cs b/src/Bifrost.Web/Pages/Mill/Vote.cshtml.cs
new file mode 100644
index 0000000..525811d
--- /dev/null
+++ b/src/Bifrost.Web/Pages/Mill/Vote.cshtml.cs
@@ -0,0 +1,132 @@
+using Bifrost.Web.Configuration;
+using Bifrost.Web.Domain;
+using Bifrost.Web.Features.ConceptVotes;
+using Bifrost.Web.Features.Shared;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Options;
+
+namespace Bifrost.Web.Pages.Mill;
+
+// The anonymous vote page. Counts and the visitor's own stance come from ConceptAppraisalService on every GET; the
+// voter key is decided by VoterIdentity.For from the actor and the device id VoterKeyResolver reads.
+[IgnoreAntiforgeryToken]
+[ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
+public sealed class VoteModel(
+ ICurrentBifrostActorAccessor actorAccessor,
+ ConceptAppraisalService appraisals,
+ VoterKeyResolver deviceCookie,
+ IOptions