137 lines
7.1 KiB
Plaintext
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() ?? "-";
|
|
}
|