137 lines
7.1 KiB
Plaintext

@model CharacterArcViewModel
@{
ViewData["Title"] = $"{Model.Character.CharacterName} Arc";
var arcScenes = Model.ArcPoints
.OrderBy(point => point.GlobalSceneIndex)
.Select(point => new
{
sceneId = point.SceneID,
axisLabel = $"Ch {point.ChapterNumber:g}.{point.SceneNumber:g}",
chapterLabel = $"Chapter {point.ChapterNumber:g}",
sceneNumber = point.SceneNumber.ToString("g"),
sceneTitle = point.SceneTitle,
bookTitle = point.BookDisplayTitle,
characterAppears = true,
characterPresence = point.IsPov ? "POV scene" : point.PresenceTypeName ?? "Appears",
timelineUrl = Url.Action("Edit", "Scenes", new { id = point.SceneID })
})
.ToList();
var relationshipIntensityPoints = Model.RelationshipEvents
.Where(item => item.Intensity.HasValue)
.GroupBy(item => item.SceneID)
.Select(group => new { sceneId = group.Key, value = Convert.ToInt32(Math.Round(group.Average(item => item.Intensity!.Value))) });
var arcGraphMetrics = new[]
{
new { metricName = "Overall Intensity", points = Model.ArcPoints.Where(point => point.OverallIntensity.HasValue).Select(point => new { sceneId = point.SceneID, value = point.OverallIntensity!.Value }) },
new { metricName = "Emotional Weight", points = Model.ArcPoints.Where(point => point.EmotionalWeight.HasValue).Select(point => new { sceneId = point.SceneID, value = point.EmotionalWeight!.Value }) },
new { metricName = "Tension", points = Model.ArcPoints.Where(point => point.Tension.HasValue).Select(point => new { sceneId = point.SceneID, value = point.Tension!.Value }) },
new { metricName = "Romance", points = Model.ArcPoints.Where(point => point.Romance.HasValue).Select(point => new { sceneId = point.SceneID, value = point.Romance!.Value }) },
new { metricName = "Action", points = Model.ArcPoints.Where(point => point.Action.HasValue).Select(point => new { sceneId = point.SceneID, value = point.Action!.Value }) },
new { metricName = "Darkness", points = Model.ArcPoints.Where(point => point.Darkness.HasValue).Select(point => new { sceneId = point.SceneID, value = point.Darkness!.Value }) },
new { metricName = "Hope / Lightness", points = Model.ArcPoints.Where(point => point.HopeLightness.HasValue).Select(point => new { sceneId = point.SceneID, value = point.HopeLightness!.Value }) },
new { metricName = "Relationship Intensity", points = relationshipIntensityPoints }
}
.Where(metric => metric.points.Any())
.ToList();
var arcGraphPayload = new
{
defaultMetrics = new[] { "Overall Intensity", "Emotional Weight", "Tension" },
scenes = arcScenes,
metrics = arcGraphMetrics
};
}
<nav class="breadcrumb-trail" aria-label="Breadcrumb">
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">@Model.Project.ProjectName</a>
<a asp-controller="Characters" asp-action="Details" asp-route-id="@Model.Character.CharacterID">@Model.Character.CharacterName</a>
<span>Character arc</span>
</nav>
<div class="page-heading">
<div>
<p class="eyebrow">Character Arc</p>
<h1>@Model.Character.CharacterName</h1>
<p class="lead-text">Appearances, emotional shape, relationship changes, knowledge gains, and advisory notes.</p>
</div>
<div class="button-row">
<a class="btn btn-outline-primary" asp-controller="Dynamics" asp-action="Knowledge" asp-route-id="@Model.Character.CharacterID">Knowledge timeline</a>
<a class="btn btn-outline-secondary" asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-FocusType="character" asp-route-FocusID="@Model.Character.CharacterID">Show on timeline</a>
</div>
</div>
<section class="story-bible-section">
<div class="story-bible-section-heading">
<div>
<p class="eyebrow">Arc Shape</p>
<h2>Character Metric Shape <help-icon key="characters.characterArc" /></h2>
</div>
<span class="soft-count">@arcGraphMetrics.Count metric@(arcGraphMetrics.Count == 1 ? "" : "s")</span>
</div>
@if (!arcGraphMetrics.Any() || !arcScenes.Any())
{
<p class="muted">Add scene metrics to scenes where @Model.Character.CharacterName appears to see their emotional and story shape.</p>
}
else
{
<div class="metric-shape-chart character-arc-chart" data-metric-shape-chart>
<p class="metric-shape-intro">Compare this character's scene metrics across appearances in narrative order. Click a point to open the scene.</p>
<div class="metric-toggle-row" data-metric-toggles></div>
<div class="metric-chart-frame">
<canvas aria-label="Comparative character arc line graph"></canvas>
<p class="muted d-none" data-chart-fallback>Character arc chart could not load. The scene markers are still listed below.</p>
</div>
<script type="application/json">@Html.Raw(System.Text.Json.JsonSerializer.Serialize(arcGraphPayload))</script>
</div>
}
</section>
<section class="story-bible-section">
<div class="story-bible-grid">
<article class="story-bible-card">
<h3>Appearance Frequency</h3>
<p class="writer-kpi">@Model.ArcPoints.Count</p>
<p class="muted">@Model.ArcPoints.Count(x => x.IsPov) POV scene@(Model.ArcPoints.Count(x => x.IsPov) == 1 ? "" : "s")</p>
</article>
<article class="story-bible-card">
<h3>Knowledge Gains</h3>
<p class="writer-kpi">@Model.KnowledgeEvents.Count</p>
</article>
<article class="story-bible-card">
<h3>Relationship Beats</h3>
<p class="writer-kpi">@Model.RelationshipEvents.Count</p>
</article>
</div>
</section>
<partial name="_DynamicsIssues" model="Model.Issues" />
<section class="story-bible-section">
<h2>Scene Markers</h2>
<div class="writer-queue-list">
@foreach (var point in Model.ArcPoints)
{
<article class="writer-queue-card">
<div class="writer-queue-title">
<h3>@point.BookDisplayTitle / Ch @point.ChapterNumber / Scene @point.SceneNumber: @point.SceneTitle</h3>
@if (point.IsPov) { <span class="status-pill">POV</span> }
</div>
<div class="story-bible-meta">
<span>Emotional @DisplayValue(point.EmotionalWeight)</span>
<span>Tension @DisplayValue(point.Tension)</span>
<span>Romance @DisplayValue(point.Romance)</span>
<span>Action @DisplayValue(point.Action)</span>
<span>@(point.RoleInSceneTypeName ?? "No role")</span>
</div>
@if (!string.IsNullOrWhiteSpace(point.EmotionalState) || !string.IsNullOrWhiteSpace(point.KeyNotes))
{
<p>@point.EmotionalState @point.KeyNotes</p>
}
</article>
}
</div>
</section>
@functions {
private static string DisplayValue(int? value) => value?.ToString() ?? "-";
}