Nick Beckley 92ab864471 Phase 2B
2026-06-03 21:56:09 +01:00

397 lines
24 KiB
Plaintext

@model CharacterDetailViewModel
@{
ViewData["Title"] = Model.Character.CharacterName;
var firstAppearance = Model.Appearances.FirstOrDefault();
var storyImportance = Model.Character.CharacterImportance switch
{
>= 8 => "Major",
>= 4 => "Secondary / Supporting",
>= 1 => "Minor",
_ => "Timeline fallback"
};
}
<nav class="breadcrumb-trail" aria-label="Breadcrumb">
<a asp-controller="Projects" asp-action="Index">Projects</a>
<a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">@Model.Project.ProjectName</a>
<a asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a>
<span>@Model.Character.CharacterName</span>
</nav>
<section class="context-header">
<div class="context-title">
<span class="eyebrow">Project context</span>
<strong>@Model.Project.ProjectName</strong>
</div>
<nav class="context-nav" aria-label="Project navigation">
<a asp-controller="Timeline" asp-action="Index" asp-route-ProjectID="@Model.Project.ProjectID" asp-route-SelectedSceneID="@firstAppearance?.SceneID" asp-route-FocusType="character" asp-route-FocusID="@Model.Character.CharacterID">Show on timeline</a>
<a asp-controller="Analytics" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Story health</a>
<a asp-controller="Warnings" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Warnings</a>
<a asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a>
</nav>
</section>
<div class="page-heading">
<div>
<p class="eyebrow">Character</p>
<h1>@Model.Character.CharacterName</h1>
<p class="lead-text">@Model.Character.DefaultDescription</p>
</div>
<div class="button-row">
<a class="btn btn-outline-secondary" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Characters</a>
<a class="btn btn-outline-secondary" asp-controller="Timeline" asp-action="Index" asp-route-ProjectID="@Model.Project.ProjectID" asp-route-SelectedSceneID="@firstAppearance?.SceneID" asp-route-FocusType="character" asp-route-FocusID="@Model.Character.CharacterID">Back to timeline</a>
<a class="btn btn-outline-primary" asp-controller="Dynamics" asp-action="Character" asp-route-id="@Model.Character.CharacterID">Character arc</a>
<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-primary" asp-controller="Exports" asp-action="Character" asp-route-id="@Model.Character.CharacterID">Export character</a>
<a class="btn btn-outline-secondary" asp-controller="Exports" asp-action="Character" asp-route-id="@Model.Character.CharacterID" asp-route-format="markdown">Markdown</a>
<a class="btn btn-outline-primary" asp-action="Edit" asp-route-id="@Model.Character.CharacterID">Edit character</a>
<form asp-action="Archive" method="post" class="archive-button-form" data-confirm-message="Archive this character?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@Model.Character.CharacterID" />
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
<button class="btn btn-outline-danger" type="submit">Archive Character</button>
</form>
</div>
</div>
@if (TempData["RelationshipMessage"] is string relationshipMessage)
{
<div class="alert alert-info">@relationshipMessage</div>
}
<section class="asset-detail-grid">
<article class="plain-card">
<h2>Profile</h2>
<p><strong>Short name:</strong> @Model.Character.ShortName</p>
<p><strong>Sex:</strong> @Model.Character.Sex</p>
<p><strong>Age:</strong> @Model.DisplayAge</p>
<p><strong>Height:</strong> @Model.Character.Height</p>
<p><strong>Eye colour:</strong> @Model.Character.EyeColour</p>
<p><strong>Story importance:</strong> @storyImportance</p>
</article>
<article class="plain-card">
<h2>Initial Relationships</h2>
@if (!Model.InitialRelationships.Any())
{
<p class="muted">No initial relationships yet.</p>
}
else
{
<div class="asset-mini-list">
@foreach (var relationship in Model.InitialRelationships)
{
var initialSourceId = relationship.CharacterAID;
var initialRelatedId = relationship.CharacterBID;
<div class="asset-mini-item">
<strong>@relationship.CharacterAName -> @relationship.CharacterBName</strong>
<span>@relationship.RelationshipTypeName</span>
<span>Reciprocal: @(relationship.IsReciprocal ? "Yes" : "No")</span>
<span>Reader initially knows: @(relationship.ReaderInitiallyKnows ? "Yes" : "No")</span>
@if (!string.IsNullOrWhiteSpace(relationship.InitialRelationshipStateName) || relationship.InitialIntensity.HasValue)
{
<span>@relationship.InitialRelationshipStateName@if (relationship.InitialIntensity.HasValue) { <text> / Intensity @relationship.InitialIntensity</text> }</span>
}
@if (!string.IsNullOrWhiteSpace(relationship.InitialBookTitle))
{
<span>Initial book: @relationship.InitialBookTitle</span>
}
@if (!string.IsNullOrWhiteSpace(relationship.Notes))
{
<span>@relationship.Notes</span>
}
<a asp-action="Details" asp-route-id="@(relationship.CharacterAID == Model.Character.CharacterID ? relationship.CharacterBID : relationship.CharacterAID)">Open related character</a>
<a asp-controller="Dynamics" asp-action="Relationship" asp-route-id="@relationship.CharacterRelationshipID">Evolution</a>
<details>
<summary>Edit</summary>
<form asp-action="SaveInitialRelationship" method="post" class="row g-2 mt-2">
<input type="hidden" name="CharacterRelationshipID" value="@relationship.CharacterRelationshipID" />
<input type="hidden" name="ProjectID" value="@Model.Project.ProjectID" />
<input type="hidden" name="CharacterID" value="@initialSourceId" />
<input type="hidden" name="ReturnCharacterID" value="@Model.Character.CharacterID" />
<div class="col-md-6">
<label class="form-label">Related character</label>
<select class="form-select form-select-sm" name="RelatedCharacterID" required>
@foreach (var option in Model.CharacterOptions.Where(x => x.Value != initialSourceId.ToString()))
{
<option value="@option.Value" selected="@(option.Value == initialRelatedId.ToString())">@option.Text</option>
}
</select>
</div>
<div class="col-md-6">
<label class="form-label">Relationship type</label>
<select class="form-select form-select-sm" name="RelationshipTypeID">
@foreach (var option in Model.RelationshipTypeOptions)
{
<option value="@option.Value" selected="@(option.Value == relationship.RelationshipTypeID.ToString())">@option.Text</option>
}
</select>
</div>
<div class="col-md-6">
<label class="form-label">Awareness</label>
<select class="form-select form-select-sm" name="InitialRelationshipStateID">
<option value="">No awareness state</option>
@foreach (var option in Model.RelationshipStateOptions)
{
<option value="@option.Value" selected="@(option.Value == relationship.InitialRelationshipStateID?.ToString())">@option.Text</option>
}
</select>
</div>
<div class="col-md-6">
<label class="form-label">Initial book</label>
<select class="form-select form-select-sm" name="InitialBookID">
<option value="">Project start</option>
@foreach (var option in Model.BookOptions)
{
<option value="@option.Value" selected="@(option.Value == relationship.InitialBookID?.ToString())">@option.Text</option>
}
</select>
</div>
<div class="col-md-8">
<label class="form-label">Confidence / intensity <output>@(relationship.InitialIntensity ?? 5)</output></label>
<input class="form-range" name="InitialIntensity" type="range" min="1" max="10" step="1" value="@(relationship.InitialIntensity ?? 5)" oninput="this.previousElementSibling.querySelector('output').value = this.value" />
</div>
<div class="col-md-4">
<label class="form-check mt-4">
<input type="hidden" name="IsReciprocal" value="false" />
<input class="form-check-input" name="IsReciprocal" type="checkbox" value="true" checked="@relationship.IsReciprocal" />
<span class="form-check-label">Reciprocal</span>
</label>
<label class="form-check">
<input type="hidden" name="ReaderInitiallyKnows" value="false" />
<input class="form-check-input" name="ReaderInitiallyKnows" type="checkbox" value="true" checked="@relationship.ReaderInitiallyKnows" />
<span class="form-check-label">Reader initially knows</span>
</label>
</div>
<div class="col-12">
<label class="form-label">Notes</label>
<textarea class="form-control form-control-sm" name="Notes" rows="2">@relationship.Notes</textarea>
</div>
<div class="col-12">
<button class="btn btn-outline-primary btn-sm" type="submit">Save initial relationship</button>
</div>
</form>
</details>
<form asp-action="ArchiveRelationship" method="post" class="archive-button-form" data-confirm-message="Archive this relationship?&#10;&#10;This will hide it from active lists and timelines, but the data will be kept and can be restored later.">
<input type="hidden" name="id" value="@relationship.CharacterRelationshipID" />
<input type="hidden" name="characterId" value="@Model.Character.CharacterID" />
<button class="btn btn-outline-danger btn-sm" type="submit">Archive / Remove</button>
</form>
</div>
}
</div>
}
<details class="mt-3">
<summary>Add initial relationship</summary>
<form asp-action="SaveInitialRelationship" method="post" class="row g-2 mt-2">
<input type="hidden" name="ProjectID" value="@Model.Project.ProjectID" />
<input type="hidden" name="CharacterID" value="@Model.Character.CharacterID" />
<input type="hidden" name="ReturnCharacterID" value="@Model.Character.CharacterID" />
<div class="col-md-6">
<label class="form-label">Related character</label>
<select class="form-select form-select-sm" name="RelatedCharacterID" required>
<option value="">Choose character</option>
@foreach (var option in Model.CharacterOptions.Where(x => x.Value != Model.Character.CharacterID.ToString()))
{
<option value="@option.Value">@option.Text</option>
}
</select>
</div>
<div class="col-md-6">
<label class="form-label">Relationship type</label>
<select class="form-select form-select-sm" name="RelationshipTypeID" asp-items="Model.RelationshipTypeOptions"></select>
</div>
<div class="col-md-6">
<label class="form-label">Awareness</label>
<select class="form-select form-select-sm" name="InitialRelationshipStateID" asp-items="Model.RelationshipStateOptions">
<option value="">No awareness state</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Initial book</label>
<select class="form-select form-select-sm" name="InitialBookID" asp-items="Model.BookOptions">
<option value="">Project start</option>
</select>
</div>
<div class="col-md-8">
<label class="form-label">Confidence / intensity <output>5</output></label>
<input class="form-range" name="InitialIntensity" type="range" min="1" max="10" step="1" value="5" oninput="this.previousElementSibling.querySelector('output').value = this.value" />
</div>
<div class="col-md-4">
<label class="form-check mt-4">
<input type="hidden" name="IsReciprocal" value="false" />
<input class="form-check-input" name="IsReciprocal" type="checkbox" value="true" checked />
<span class="form-check-label">Reciprocal</span>
</label>
<label class="form-check">
<input type="hidden" name="ReaderInitiallyKnows" value="false" />
<input class="form-check-input" name="ReaderInitiallyKnows" type="checkbox" value="true" checked />
<span class="form-check-label">Reader initially knows</span>
</label>
</div>
<div class="col-12">
<label class="form-label">Notes</label>
<textarea class="form-control form-control-sm" name="Notes" rows="2"></textarea>
</div>
<div class="col-12">
<button class="btn btn-outline-primary btn-sm" type="submit">Add initial relationship</button>
</div>
</form>
</details>
</article>
</section>
<section class="list-section mt-3">
<h2>Current Relationships</h2>
@if (!Model.CurrentRelationships.Any())
{
<p class="muted">No current relationship state yet.</p>
}
else
{
<div class="asset-mini-list">
@foreach (var current in Model.CurrentRelationships)
{
var relationship = current.Relationship;
var relatedCharacterId = relationship.CharacterAID == Model.Character.CharacterID ? relationship.CharacterBID : relationship.CharacterAID;
var relatedCharacterName = relationship.CharacterAID == Model.Character.CharacterID ? relationship.CharacterBName : relationship.CharacterAName;
<div class="asset-mini-item">
<strong>@relatedCharacterName</strong>
<span>@relationship.RelationshipTypeName · Current: @current.CurrentStateName</span>
<span>Reciprocal: @(relationship.IsReciprocal ? "Yes" : "No") · Confidence / intensity: @(current.CurrentIntensity?.ToString() ?? "Unknown")</span>
@if (!string.IsNullOrWhiteSpace(current.CurrentNotes))
{
<span>@current.CurrentNotes</span>
}
<a asp-action="Details" asp-route-id="@relatedCharacterId">Open related character</a>
<a asp-controller="Dynamics" asp-action="Relationship" asp-route-id="@relationship.CharacterRelationshipID">Evolution</a>
<details>
<summary>View History</summary>
<div class="asset-mini-list mt-2">
<div class="asset-mini-item">
<strong>Initial: @relationship.RelationshipTypeName</strong>
<span>@(relationship.InitialRelationshipStateName ?? "No awareness state")@if (relationship.InitialIntensity.HasValue) { <text> · Intensity @relationship.InitialIntensity</text> }</span>
<span>Reader initially knows: @(relationship.ReaderInitiallyKnows ? "Yes" : "No")</span>
@if (!string.IsNullOrWhiteSpace(relationship.Notes))
{
<span>@relationship.Notes</span>
}
</div>
@foreach (var item in current.Events)
{
<div class="asset-mini-item">
<strong>@item.BookTitle / Ch @item.ChapterNumber / Scene @item.SceneNumber: @item.SceneTitle</strong>
<span>@item.RelationshipStateName · Intensity @(item.Intensity?.ToString() ?? "Unknown")</span>
<span>Awareness: @(item.IsKnownToOtherCharacter ? "known to other character" : "not known to other character")</span>
@if (!string.IsNullOrWhiteSpace(item.Description))
{
<span>@item.Description</span>
}
<a asp-controller="Timeline" asp-action="Index" asp-route-ProjectID="@Model.Project.ProjectID" asp-route-SelectedSceneID="@item.SceneID" asp-route-FocusType="character" asp-route-FocusID="@Model.Character.CharacterID">Open scene</a>
</div>
}
<div class="asset-mini-item">
<strong>Current: @current.CurrentStateName</strong>
<span>Confidence / intensity: @(current.CurrentIntensity?.ToString() ?? "Unknown")</span>
</div>
</div>
</details>
</div>
}
</div>
}
</section>
<section class="list-section mt-3">
<h2>Relationship Events</h2>
@if (!Model.RelationshipEvents.Any())
{
<p class="muted">No relationship events yet.</p>
}
else
{
<div class="asset-mini-list">
@foreach (var item in Model.RelationshipEvents)
{
<div class="asset-mini-item">
<strong>@item.CharacterAName -> @item.CharacterBName: @item.RelationshipStateName</strong>
<span>@item.BookTitle / Ch @item.ChapterNumber / Scene @item.SceneNumber: @item.SceneTitle</span>
<span>@item.RelationshipTypeName · Awareness: @(item.IsKnownToOtherCharacter ? "known to other character" : "not known to other character")</span>
<span>Reciprocal: @(item.IsReciprocal ? "Yes" : "No") · Confidence / intensity: @(item.Intensity?.ToString() ?? "Unknown")</span>
@if (!string.IsNullOrWhiteSpace(item.Description))
{
<span>@item.Description</span>
}
<a asp-controller="Timeline" asp-action="Index" asp-route-ProjectID="@Model.Project.ProjectID" asp-route-SelectedSceneID="@item.SceneID" asp-route-FocusType="character" asp-route-FocusID="@Model.Character.CharacterID">Open in timeline</a>
<a asp-action="Details" asp-route-id="@(item.CharacterAID == Model.Character.CharacterID ? item.CharacterBID : item.CharacterAID)">Open related character</a>
</div>
}
</div>
}
</section>
<section class="list-section mt-3">
<h2>Scene appearances</h2>
@if (!Model.Appearances.Any())
{
<p class="muted">No scene appearances yet.</p>
}
else
{
<div class="asset-mini-list">
@foreach (var appearance in Model.Appearances)
{
<div class="asset-mini-item">
<strong>@appearance.BookTitle / Ch @appearance.ChapterNumber / Scene @appearance.SceneNumber: @appearance.SceneTitle</strong>
<span>@appearance.RoleInSceneTypeName / @appearance.PresenceTypeName</span>
<span>@appearance.OutfitDescription @appearance.PhysicalCondition @appearance.EmotionalState</span>
<a asp-controller="Timeline" asp-action="Index" asp-route-ProjectID="@Model.Project.ProjectID" asp-route-SelectedSceneID="@appearance.SceneID" asp-route-FocusType="character" asp-route-FocusID="@Model.Character.CharacterID">Open in timeline</a>
</div>
}
</div>
}
</section>
<section class="list-section mt-3">
<h2>Attribute history</h2>
@if (!Model.AttributeEvents.Any())
{
<p class="muted">No attribute events yet.</p>
}
else
{
<div class="asset-mini-list">
@foreach (var item in Model.AttributeEvents)
{
<div class="asset-mini-item">
<strong>@item.AttributeName: @item.AttributeValue</strong>
<span>@item.BookTitle / Ch @item.ChapterNumber / Scene @item.SceneNumber: @item.SceneTitle</span>
<span>@item.Description</span>
<a asp-controller="Timeline" asp-action="Index" asp-route-ProjectID="@Model.Project.ProjectID" asp-route-SelectedSceneID="@item.SceneID" asp-route-FocusType="character" asp-route-FocusID="@Model.Character.CharacterID">Open in timeline</a>
</div>
}
</div>
}
</section>
<section class="list-section mt-3">
<h2>Knowledge</h2>
@if (!Model.KnowledgeItems.Any())
{
<p class="muted">No knowledge entries yet.</p>
}
else
{
<div class="asset-mini-list">
@foreach (var item in Model.KnowledgeItems)
{
<div class="asset-mini-item">
<strong>@item.KnowledgeStateName: @(item.AssetName ?? item.ThreadTitle)</strong>
<span>@item.BookTitle / Ch @item.ChapterNumber / Scene @item.SceneNumber: @item.SceneTitle</span>
<span>@item.Description</span>
<a asp-controller="Timeline" asp-action="Index" asp-route-ProjectID="@Model.Project.ProjectID" asp-route-SelectedSceneID="@item.SceneID" asp-route-FocusType="character" asp-route-FocusID="@Model.Character.CharacterID">Open in timeline</a>
</div>
}
</div>
}
</section>