Improve onboarding import wizard UX
This commit is contained in:
parent
c3043b994a
commit
72d2438314
@ -1149,10 +1149,10 @@ static void Phase21UEnforcesAliasAgeSemanticAndUiResetRules()
|
|||||||
Assert(onboarding.Contains("name=\"primaryAgeGroup\"", StringComparison.Ordinal), "Import setup must ask for primary age group.");
|
Assert(onboarding.Contains("name=\"primaryAgeGroup\"", StringComparison.Ordinal), "Import setup must ask for primary age group.");
|
||||||
Assert(onboarding.Contains("name=\"storyEra\"", StringComparison.Ordinal), "Import setup must ask for era.");
|
Assert(onboarding.Contains("name=\"storyEra\"", StringComparison.Ordinal), "Import setup must ask for era.");
|
||||||
Assert(onboarding.Contains("name=\"storyLocation\"", StringComparison.Ordinal), "Import setup must ask for location.");
|
Assert(onboarding.Contains("name=\"storyLocation\"", StringComparison.Ordinal), "Import setup must ask for location.");
|
||||||
Assert(onboarding.Contains("<select class=\"form-control\" name=\"appearancePreference\"", StringComparison.Ordinal), "Appearance guidance must be a dropdown, not a radio list.");
|
Assert(onboarding.Contains("<select class=\"form-select\" name=\"appearancePreference\"", StringComparison.Ordinal), "Appearance guidance must be a dropdown, not a radio list.");
|
||||||
Assert(onboarding.Contains("<select class=\"form-control\" name=\"primaryAgeGroup\"", StringComparison.Ordinal), "Primary age group must be a dropdown.");
|
Assert(onboarding.Contains("<select class=\"form-select\" name=\"primaryAgeGroup\"", StringComparison.Ordinal), "Primary age group must be a dropdown.");
|
||||||
Assert(onboarding.Contains("<select class=\"form-control\" name=\"storyEra\"", StringComparison.Ordinal), "Story era must be a dropdown.");
|
Assert(onboarding.Contains("<select class=\"form-select\" name=\"storyEra\"", StringComparison.Ordinal), "Story era must be a dropdown.");
|
||||||
Assert(onboarding.Contains("<select class=\"form-control\" name=\"storyLocation\"", StringComparison.Ordinal), "Story location must be a dropdown.");
|
Assert(onboarding.Contains("<select class=\"form-select\" name=\"storyLocation\"", StringComparison.Ordinal), "Story location must be a dropdown.");
|
||||||
Assert(storyMemory.Contains("CharacterAppearanceExtractionService.Extract", StringComparison.Ordinal), "Durable Story Memory must consume dedicated appearance extraction.");
|
Assert(storyMemory.Contains("CharacterAppearanceExtractionService.Extract", StringComparison.Ordinal), "Durable Story Memory must consume dedicated appearance extraction.");
|
||||||
Assert(storyMemory.Contains("SkinToneInstruction(skinTone)", StringComparison.Ordinal), "Demand generation must use hard skin-tone wording.");
|
Assert(storyMemory.Contains("SkinToneInstruction(skinTone)", StringComparison.Ordinal), "Demand generation must use hard skin-tone wording.");
|
||||||
Assert(storyMemory.Contains("HairColours.Red", StringComparison.Ordinal) && storyMemory.Contains("SkinTones.Light", StringComparison.Ordinal), "Red-hair demand evidence must force light skin when skin is otherwise unknown.");
|
Assert(storyMemory.Contains("HairColours.Red", StringComparison.Ordinal) && storyMemory.Contains("SkinTones.Light", StringComparison.Ordinal), "Red-hair demand evidence must force light skin when skin is otherwise unknown.");
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
.Where(candidate => !string.Equals(candidate.Category, "Excluded", StringComparison.OrdinalIgnoreCase))
|
.Where(candidate => !string.Equals(candidate.Category, "Excluded", StringComparison.OrdinalIgnoreCase))
|
||||||
.OrderBy(candidate => candidate.ReviewName)
|
.OrderBy(candidate => candidate.ReviewName)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
var visibleCharacterCount = probableCharacters.Count + possibleCharacters.Count + relationshipTitles.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
<section class="onboarding-shell" aria-labelledby="scan-review-title">
|
<section class="onboarding-shell" aria-labelledby="scan-review-title">
|
||||||
@ -27,9 +28,8 @@
|
|||||||
|
|
||||||
<div class="onboarding-copy">
|
<div class="onboarding-copy">
|
||||||
<p class="eyebrow">Step 7 of 9</p>
|
<p class="eyebrow">Step 7 of 9</p>
|
||||||
<h1 id="scan-review-title">Review Chapters</h1>
|
<h1 id="scan-review-title">Review Manuscript Scan</h1>
|
||||||
<p>Review the chapters PlotDirector found before Story Intelligence begins.</p>
|
<p>Confirm the chapter structure first, then review the detected character names and aliases before Story Intelligence begins.</p>
|
||||||
<p>Scene and character scan details are shown for context only in this phase.</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (TempData["OnboardingReviewMessage"] is string reviewMessage)
|
@if (TempData["OnboardingReviewMessage"] is string reviewMessage)
|
||||||
@ -65,8 +65,13 @@
|
|||||||
<button class="btn btn-sm btn-outline-secondary" type="reset">Restore scan defaults</button>
|
<button class="btn btn-sm btn-outline-secondary" type="reset">Restore scan defaults</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="onboarding-review-grid" aria-label="Scan preview">
|
<section class="onboarding-review-workflow" aria-label="Scan preview" data-scan-review-tabs>
|
||||||
<div class="onboarding-review-section">
|
<div class="onboarding-review-tabs" role="tablist" aria-label="Scan review sections">
|
||||||
|
<button class="is-active" type="button" role="tab" aria-selected="true" data-scan-review-tab="structure">1. Chapters and scenes</button>
|
||||||
|
<button type="button" role="tab" aria-selected="false" data-scan-review-tab="characters">2. Characters</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="onboarding-review-section" data-scan-review-panel="structure">
|
||||||
<h2>Chapters and scenes</h2>
|
<h2>Chapters and scenes</h2>
|
||||||
@if (!Model.Chapters.Any())
|
@if (!Model.Chapters.Any())
|
||||||
{
|
{
|
||||||
@ -144,10 +149,19 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
<div class="onboarding-actions onboarding-actions--inline">
|
||||||
|
<button class="btn btn-primary" type="button" data-scan-review-next="characters">Continue to characters</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside class="onboarding-review-section">
|
<aside class="onboarding-review-section" data-scan-review-panel="characters" hidden>
|
||||||
<h2>Character candidates</h2>
|
<div class="onboarding-review-section-heading">
|
||||||
|
<div>
|
||||||
|
<h2>Character candidates</h2>
|
||||||
|
<p>@visibleCharacterCount.ToString("N0") detected candidate@(visibleCharacterCount == 1 ? string.Empty : "s") before excluded names.</p>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-outline-secondary btn-sm" type="button" data-scan-review-next="structure">Back to chapters</button>
|
||||||
|
</div>
|
||||||
@if (!probableCharacters.Any() && !possibleCharacters.Any() && !relationshipTitles.Any())
|
@if (!probableCharacters.Any() && !possibleCharacters.Any() && !relationshipTitles.Any())
|
||||||
{
|
{
|
||||||
<p>No repeated character names were found yet. You can still prepare chapters and add characters later.</p>
|
<p>No repeated character names were found yet. You can still prepare chapters and add characters later.</p>
|
||||||
@ -159,6 +173,11 @@
|
|||||||
Find a candidate
|
Find a candidate
|
||||||
<input class="form-control" type="search" placeholder="Search names or notes" data-character-filter />
|
<input class="form-control" type="search" placeholder="Search names or notes" data-character-filter />
|
||||||
</label>
|
</label>
|
||||||
|
<div class="onboarding-character-pager" data-character-pager hidden>
|
||||||
|
<button class="btn btn-outline-secondary btn-sm" type="button" data-character-page-prev>Previous</button>
|
||||||
|
<span data-character-page-label></span>
|
||||||
|
<button class="btn btn-outline-secondary btn-sm" type="button" data-character-page-next>Next</button>
|
||||||
|
</div>
|
||||||
<p class="onboarding-empty-state" data-character-empty hidden>No matching candidates.</p>
|
<p class="onboarding-empty-state" data-character-empty hidden>No matching candidates.</p>
|
||||||
<div class="onboarding-character-groups">
|
<div class="onboarding-character-groups">
|
||||||
@foreach (var group in new[]
|
@foreach (var group in new[]
|
||||||
@ -178,7 +197,7 @@
|
|||||||
<ul class="onboarding-character-candidates">
|
<ul class="onboarding-character-candidates">
|
||||||
@foreach (var candidate in group.Candidates)
|
@foreach (var candidate in group.Candidates)
|
||||||
{
|
{
|
||||||
<li data-character-name="@candidate.ReviewName" data-character-reason="@candidate.Reason">
|
<li data-character-name="@candidate.ReviewName" data-character-reason="@candidate.Reason" data-character-page-item>
|
||||||
<input type="hidden" name="Characters[@characterIndex].TemporaryCharacterKey" value="@candidate.TemporaryCharacterKey" />
|
<input type="hidden" name="Characters[@characterIndex].TemporaryCharacterKey" value="@candidate.TemporaryCharacterKey" />
|
||||||
<input type="hidden" name="Characters[@characterIndex].Category" value="@candidate.Category" />
|
<input type="hidden" name="Characters[@characterIndex].Category" value="@candidate.Category" />
|
||||||
<label class="onboarding-review-toggle">
|
<label class="onboarding-review-toggle">
|
||||||
@ -238,7 +257,7 @@
|
|||||||
<ul class="onboarding-character-candidates">
|
<ul class="onboarding-character-candidates">
|
||||||
@foreach (var candidate in excludedCharacters)
|
@foreach (var candidate in excludedCharacters)
|
||||||
{
|
{
|
||||||
<li data-character-name="@candidate.ReviewName" data-character-reason="@candidate.Reason">
|
<li data-character-name="@candidate.ReviewName" data-character-reason="@candidate.Reason" data-character-page-item>
|
||||||
<input type="hidden" name="Characters[@characterIndex].TemporaryCharacterKey" value="@candidate.TemporaryCharacterKey" />
|
<input type="hidden" name="Characters[@characterIndex].TemporaryCharacterKey" value="@candidate.TemporaryCharacterKey" />
|
||||||
<input type="hidden" name="Characters[@characterIndex].Category" value="@candidate.Category" />
|
<input type="hidden" name="Characters[@characterIndex].Category" value="@candidate.Category" />
|
||||||
<label class="onboarding-review-toggle">
|
<label class="onboarding-review-toggle">
|
||||||
@ -299,6 +318,33 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
const scanReviewRoot = document.querySelector("[data-scan-review-tabs]");
|
||||||
|
const showScanPanel = (panelName) => {
|
||||||
|
if (!scanReviewRoot) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scanReviewRoot.querySelectorAll("[data-scan-review-tab]").forEach((tab) => {
|
||||||
|
const isActive = tab.dataset.scanReviewTab === panelName;
|
||||||
|
tab.classList.toggle("is-active", isActive);
|
||||||
|
tab.setAttribute("aria-selected", String(isActive));
|
||||||
|
});
|
||||||
|
scanReviewRoot.querySelectorAll("[data-scan-review-panel]").forEach((panel) => {
|
||||||
|
panel.hidden = panel.dataset.scanReviewPanel !== panelName;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
scanReviewRoot?.addEventListener("click", (event) => {
|
||||||
|
const tab = event.target.closest("[data-scan-review-tab]");
|
||||||
|
if (tab) {
|
||||||
|
showScanPanel(tab.dataset.scanReviewTab);
|
||||||
|
}
|
||||||
|
|
||||||
|
const next = event.target.closest("[data-scan-review-next]");
|
||||||
|
if (next) {
|
||||||
|
showScanPanel(next.dataset.scanReviewNext);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const reviewTools = document.querySelector("[data-review-tools]");
|
const reviewTools = document.querySelector("[data-review-tools]");
|
||||||
reviewTools?.addEventListener("click", (event) => {
|
reviewTools?.addEventListener("click", (event) => {
|
||||||
const button = event.target.closest("[data-bulk-target]");
|
const button = event.target.closest("[data-bulk-target]");
|
||||||
@ -314,26 +360,79 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const characterFilter = document.querySelector("[data-character-filter]");
|
const characterFilter = document.querySelector("[data-character-filter]");
|
||||||
characterFilter?.addEventListener("input", () => {
|
const characterPager = document.querySelector("[data-character-pager]");
|
||||||
const query = characterFilter.value.trim().toLowerCase();
|
const characterPageLabel = document.querySelector("[data-character-page-label]");
|
||||||
let visibleCount = 0;
|
const characterPagePrev = document.querySelector("[data-character-page-prev]");
|
||||||
document.querySelectorAll("[data-character-name]").forEach((item) => {
|
const characterPageNext = document.querySelector("[data-character-page-next]");
|
||||||
const text = `${item.dataset.characterName || ""} ${item.dataset.characterReason || ""}`.toLowerCase();
|
const characterPageSize = 20;
|
||||||
const isVisible = query.length === 0 || text.includes(query);
|
let characterPage = 1;
|
||||||
item.hidden = !isVisible;
|
|
||||||
if (isVisible) {
|
|
||||||
visibleCount++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
const characterItems = () => [...document.querySelectorAll("[data-character-page-item]")];
|
||||||
|
const matchingCharacterItems = () => {
|
||||||
|
const query = characterFilter?.value.trim().toLowerCase() || "";
|
||||||
|
return characterItems().filter((item) => {
|
||||||
|
const text = `${item.dataset.characterName || ""} ${item.dataset.characterReason || ""}`.toLowerCase();
|
||||||
|
return query.length === 0 || text.includes(query);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshCharacterSections = () => {
|
||||||
document.querySelectorAll(".onboarding-character-groups section").forEach((section) => {
|
document.querySelectorAll(".onboarding-character-groups section").forEach((section) => {
|
||||||
const hasVisibleItems = [...section.querySelectorAll("[data-character-name]")].some((item) => !item.hidden);
|
const hasVisibleItems = [...section.querySelectorAll("[data-character-page-item]")].some((item) => !item.hidden);
|
||||||
section.hidden = !hasVisibleItems;
|
section.hidden = !hasVisibleItems;
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCharacterPage = () => {
|
||||||
|
const matches = matchingCharacterItems();
|
||||||
|
const pageCount = Math.max(1, Math.ceil(matches.length / characterPageSize));
|
||||||
|
characterPage = Math.min(Math.max(characterPage, 1), pageCount);
|
||||||
|
const start = (characterPage - 1) * characterPageSize;
|
||||||
|
const visible = new Set(matches.slice(start, start + characterPageSize));
|
||||||
|
|
||||||
|
characterItems().forEach((item) => {
|
||||||
|
item.hidden = !visible.has(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (characterPager) {
|
||||||
|
characterPager.hidden = matches.length <= characterPageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (characterPageLabel) {
|
||||||
|
characterPageLabel.textContent = matches.length === 0
|
||||||
|
? "No candidates"
|
||||||
|
: `Page ${characterPage} of ${pageCount} / ${matches.length.toLocaleString()} candidate${matches.length === 1 ? "" : "s"}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (characterPagePrev) {
|
||||||
|
characterPagePrev.disabled = characterPage <= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (characterPageNext) {
|
||||||
|
characterPageNext.disabled = characterPage >= pageCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshCharacterSections();
|
||||||
const emptyState = document.querySelector("[data-character-empty]");
|
const emptyState = document.querySelector("[data-character-empty]");
|
||||||
if (emptyState) {
|
if (emptyState) {
|
||||||
emptyState.hidden = visibleCount > 0;
|
emptyState.hidden = matches.length > 0;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
characterFilter?.addEventListener("input", () => {
|
||||||
|
characterPage = 1;
|
||||||
|
renderCharacterPage();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
characterPagePrev?.addEventListener("click", () => {
|
||||||
|
characterPage -= 1;
|
||||||
|
renderCharacterPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
characterPageNext?.addEventListener("click", () => {
|
||||||
|
characterPage += 1;
|
||||||
|
renderCharacterPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
renderCharacterPage();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -4,12 +4,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<section class="onboarding-shell" aria-labelledby="story-intelligence-title">
|
<section class="onboarding-shell" aria-labelledby="story-intelligence-title">
|
||||||
<div class="onboarding-panel">
|
<div class="onboarding-panel onboarding-analysis-panel">
|
||||||
<partial name="_StoryIntelligencePipelineHeader" model="@(new StoryIntelligencePipelineHeaderViewModel { CurrentStage = "Analyse Manuscript" })" />
|
<partial name="_OnboardingJourneyHeader" model="@(new OnboardingJourneyHeaderViewModel { CurrentStep = 8 })" />
|
||||||
<div class="onboarding-copy">
|
<div class="onboarding-copy">
|
||||||
<p class="eyebrow">Analyse manuscript</p>
|
<p class="eyebrow">Step 8 of 9</p>
|
||||||
<h1 id="story-intelligence-title">Analyse Manuscript</h1>
|
<h1 id="story-intelligence-title">Analyse Manuscript</h1>
|
||||||
<p>PlotDirector will analyse approved chapters in the background and prepare scene suggestions for review.</p>
|
<p>Set the analysis context, then PlotDirector will read the approved chapters and prepare scenes, characters, locations, assets, relationships and knowledge for review.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (TempData["OnboardingStoryIntelligenceError"] is string error)
|
@if (TempData["OnboardingStoryIntelligenceError"] is string error)
|
||||||
@ -59,14 +59,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="onboarding-form-section">
|
|
||||||
<p>Nothing will be created yet. PlotDirector will prepare scene suggestions for review.</p>
|
|
||||||
<p>Scenes are reviewed first. Character review follows after scenes are created, with locations, assets, relationships and knowledge shown as upcoming Story Intelligence stages.</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
@if (!string.IsNullOrWhiteSpace(Model.Message))
|
@if (!string.IsNullOrWhiteSpace(Model.Message))
|
||||||
{
|
{
|
||||||
<section class="onboarding-form-section">
|
<section class="onboarding-form-section onboarding-form-section--quiet">
|
||||||
<p>@Model.Message</p>
|
<p>@Model.Message</p>
|
||||||
@if (Model.MissingChapterTextCount > 0)
|
@if (Model.MissingChapterTextCount > 0)
|
||||||
{
|
{
|
||||||
@ -75,74 +70,88 @@
|
|||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="onboarding-actions">
|
<form asp-action="StartStoryIntelligence" method="post" class="onboarding-analysis-setup">
|
||||||
<a class="btn btn-outline-secondary" asp-action="ScanReview" asp-route-previewId="@Model.PreviewID">Back to chapter review</a>
|
<div class="onboarding-analysis-setup__header">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Analysis setup</p>
|
||||||
|
<h2>Story and portrait context</h2>
|
||||||
|
<p>Explicit manuscript evidence always wins. These choices guide unresolved generated portraits and broad story context.</p>
|
||||||
|
</div>
|
||||||
|
<strong>Required before analysis</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="onboarding-analysis-controls">
|
||||||
|
<label>
|
||||||
|
Appearance baseline
|
||||||
|
<select class="form-select" name="appearancePreference">
|
||||||
|
<option value="@StoryMemoryAppearancePreferences.Balanced">Balanced diverse cast</option>
|
||||||
|
<option value="@StoryMemoryAppearancePreferences.ExplicitEvidenceOnly">Explicit evidence only</option>
|
||||||
|
<option value="@StoryMemoryAppearancePreferences.PredominantlyLight">Predominantly light-skinned cast</option>
|
||||||
|
<option value="@StoryMemoryAppearancePreferences.PredominantlyMedium">Predominantly medium-skinned cast</option>
|
||||||
|
<option value="@StoryMemoryAppearancePreferences.PredominantlyDark">Predominantly dark-skinned cast</option>
|
||||||
|
<option value="@StoryMemoryAppearancePreferences.CustomMix">Custom mix</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Primary character age group
|
||||||
|
<select class="form-select" name="primaryAgeGroup">
|
||||||
|
<option value="">No baseline</option>
|
||||||
|
<option value="Late teen / early 20s">Late teen / early 20s</option>
|
||||||
|
<option value="Young teen">Young teen</option>
|
||||||
|
<option value="Older teen">Older teen</option>
|
||||||
|
<option value="Young adult">Young adult</option>
|
||||||
|
<option value="Adult">Adult</option>
|
||||||
|
<option value="Middle-aged adult">Middle-aged adult</option>
|
||||||
|
<option value="Senior">Senior</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Era
|
||||||
|
<select class="form-select" name="storyEra">
|
||||||
|
<option value="">No baseline</option>
|
||||||
|
<option value="1980s">1980s</option>
|
||||||
|
<option value="1970s">1970s</option>
|
||||||
|
<option value="1990s">1990s</option>
|
||||||
|
<option value="2000s">2000s</option>
|
||||||
|
<option value="Contemporary">Contemporary</option>
|
||||||
|
<option value="Victorian">Victorian</option>
|
||||||
|
<option value="Medieval">Medieval</option>
|
||||||
|
<option value="Near future">Near future</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Location
|
||||||
|
<select class="form-select" name="storyLocation">
|
||||||
|
<option value="">No baseline</option>
|
||||||
|
<option value="UK">UK</option>
|
||||||
|
<option value="United States">United States</option>
|
||||||
|
<option value="Europe">Europe</option>
|
||||||
|
<option value="Urban">Urban</option>
|
||||||
|
<option value="Suburban">Suburban</option>
|
||||||
|
<option value="Rural">Rural</option>
|
||||||
|
<option value="Fantasy world">Fantasy world</option>
|
||||||
|
<option value="Science fiction setting">Science fiction setting</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="onboarding-analysis-flow">
|
||||||
|
<div><span>1</span><strong>Analyse chapters</strong></div>
|
||||||
|
<div><span>2</span><strong>Review scenes</strong></div>
|
||||||
|
<div><span>3</span><strong>Review story entities</strong></div>
|
||||||
|
<div><span>4</span><strong>Build database</strong></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="onboarding-actions">
|
||||||
|
<a class="btn btn-outline-secondary" asp-action="ScanReview" asp-route-previewId="@Model.PreviewID">Back to scan review</a>
|
||||||
|
<button class="btn btn-primary" type="submit" disabled="@(!Model.CanStart)">Analyse manuscript</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="onboarding-actions onboarding-actions--secondary">
|
||||||
<form asp-action="SkipStoryIntelligence" method="post">
|
<form asp-action="SkipStoryIntelligence" method="post">
|
||||||
<button class="btn btn-outline-secondary" type="submit">Skip for now</button>
|
<button class="btn btn-outline-secondary" type="submit">Skip for now</button>
|
||||||
</form>
|
</form>
|
||||||
<form asp-action="StartStoryIntelligence" method="post" class="onboarding-form-section">
|
|
||||||
<fieldset>
|
|
||||||
<legend>Unresolved character appearance</legend>
|
|
||||||
<p>Explicit manuscript evidence always wins. This only guides characters whose appearance is not described.</p>
|
|
||||||
<label>
|
|
||||||
Appearance baseline
|
|
||||||
<select class="form-control" name="appearancePreference">
|
|
||||||
<option value="@StoryMemoryAppearancePreferences.Balanced">Balanced diverse cast</option>
|
|
||||||
<option value="@StoryMemoryAppearancePreferences.ExplicitEvidenceOnly">Explicit evidence only</option>
|
|
||||||
<option value="@StoryMemoryAppearancePreferences.PredominantlyLight">Predominantly light-skinned cast</option>
|
|
||||||
<option value="@StoryMemoryAppearancePreferences.PredominantlyMedium">Predominantly medium-skinned cast</option>
|
|
||||||
<option value="@StoryMemoryAppearancePreferences.PredominantlyDark">Predominantly dark-skinned cast</option>
|
|
||||||
<option value="@StoryMemoryAppearancePreferences.CustomMix">Custom mix</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset>
|
|
||||||
<legend>Story context for generated portraits</legend>
|
|
||||||
<p>These guide generated images when the manuscript is silent. Explicit manuscript evidence still wins.</p>
|
|
||||||
<label>
|
|
||||||
Primary character age group
|
|
||||||
<select class="form-control" name="primaryAgeGroup">
|
|
||||||
<option value="">No baseline</option>
|
|
||||||
<option value="Late teen / early 20s">Late teen / early 20s</option>
|
|
||||||
<option value="Young teen">Young teen</option>
|
|
||||||
<option value="Older teen">Older teen</option>
|
|
||||||
<option value="Young adult">Young adult</option>
|
|
||||||
<option value="Adult">Adult</option>
|
|
||||||
<option value="Middle-aged adult">Middle-aged adult</option>
|
|
||||||
<option value="Senior">Senior</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Era
|
|
||||||
<select class="form-control" name="storyEra">
|
|
||||||
<option value="">No baseline</option>
|
|
||||||
<option value="1980s">1980s</option>
|
|
||||||
<option value="1970s">1970s</option>
|
|
||||||
<option value="1990s">1990s</option>
|
|
||||||
<option value="2000s">2000s</option>
|
|
||||||
<option value="Contemporary">Contemporary</option>
|
|
||||||
<option value="Victorian">Victorian</option>
|
|
||||||
<option value="Medieval">Medieval</option>
|
|
||||||
<option value="Near future">Near future</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Location
|
|
||||||
<select class="form-control" name="storyLocation">
|
|
||||||
<option value="">No baseline</option>
|
|
||||||
<option value="UK">UK</option>
|
|
||||||
<option value="United States">United States</option>
|
|
||||||
<option value="Europe">Europe</option>
|
|
||||||
<option value="Urban">Urban</option>
|
|
||||||
<option value="Suburban">Suburban</option>
|
|
||||||
<option value="Rural">Rural</option>
|
|
||||||
<option value="Fantasy world">Fantasy world</option>
|
|
||||||
<option value="Science fiction setting">Science fiction setting</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
</fieldset>
|
|
||||||
<button class="btn btn-primary" type="submit" disabled="@(!Model.CanStart)">Analyse manuscript</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -222,6 +222,15 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.onboarding-actions--inline {
|
||||||
|
margin-top: .75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-actions--secondary {
|
||||||
|
justify-content: flex-start;
|
||||||
|
margin-top: .9rem;
|
||||||
|
}
|
||||||
|
|
||||||
.onboarding-form-section {
|
.onboarding-form-section {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: .75rem;
|
gap: .75rem;
|
||||||
@ -232,6 +241,10 @@
|
|||||||
background: rgba(255, 255, 255, .45);
|
background: rgba(255, 255, 255, .45);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.onboarding-form-section--quiet {
|
||||||
|
background: rgba(255, 255, 255, .58);
|
||||||
|
}
|
||||||
|
|
||||||
.onboarding-form-section h2 {
|
.onboarding-form-section h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1.05rem;
|
font-size: 1.05rem;
|
||||||
@ -464,10 +477,46 @@
|
|||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.onboarding-review-workflow {
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-review-tabs {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: .35rem;
|
||||||
|
border: 1px solid rgba(31, 42, 68, .14);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: .35rem;
|
||||||
|
background: rgba(255, 255, 255, .58);
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-review-tabs button {
|
||||||
|
min-height: 2.7rem;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
|
font: inherit;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-review-tabs button.is-active {
|
||||||
|
border-color: rgba(47, 111, 99, .34);
|
||||||
|
background: rgba(47, 111, 99, .11);
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(47, 111, 99, .08);
|
||||||
|
}
|
||||||
|
|
||||||
.onboarding-review-section {
|
.onboarding-review-section {
|
||||||
display: grid;
|
display: grid;
|
||||||
align-content: start;
|
align-content: start;
|
||||||
gap: .8rem;
|
gap: .8rem;
|
||||||
|
border: 1px solid rgba(31, 42, 68, .1);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
background: rgba(255, 255, 255, .42);
|
||||||
}
|
}
|
||||||
|
|
||||||
.onboarding-review-section h2 {
|
.onboarding-review-section h2 {
|
||||||
@ -475,6 +524,18 @@
|
|||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.onboarding-review-section-heading {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-review-section-heading p {
|
||||||
|
margin: .25rem 0 0;
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
.onboarding-character-filter {
|
.onboarding-character-filter {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: .35rem;
|
gap: .35rem;
|
||||||
@ -1285,6 +1346,23 @@ summary.story-review-chapter-heading {
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.onboarding-character-pager {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: .75rem;
|
||||||
|
border: 1px solid rgba(31, 42, 68, .1);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: .6rem;
|
||||||
|
background: rgba(255, 255, 255, .62);
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-character-pager span {
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
|
font-weight: 800;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.onboarding-excluded-candidates {
|
.onboarding-excluded-candidates {
|
||||||
border-top: 1px solid rgba(31, 42, 68, .12);
|
border-top: 1px solid rgba(31, 42, 68, .12);
|
||||||
padding-top: .65rem;
|
padding-top: .65rem;
|
||||||
@ -1419,6 +1497,105 @@ summary.story-review-chapter-heading {
|
|||||||
background: rgba(47, 111, 99, .07);
|
background: rgba(47, 111, 99, .07);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.onboarding-analysis-panel {
|
||||||
|
width: min(1040px, 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-analysis-setup {
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
border: 1px solid rgba(47, 111, 99, .28);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: clamp(1rem, 2vw, 1.35rem);
|
||||||
|
background: linear-gradient(135deg, rgba(47, 111, 99, .1), rgba(255, 255, 255, .72) 52%, rgba(182, 138, 70, .12));
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .54);
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-analysis-setup__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-analysis-setup__header h2 {
|
||||||
|
margin: 0 0 .35rem;
|
||||||
|
font-size: 1.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-analysis-setup__header p:not(.eyebrow) {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-analysis-setup__header > strong {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
border: 1px solid rgba(47, 111, 99, .22);
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: .35rem .7rem;
|
||||||
|
background: rgba(255, 255, 255, .72);
|
||||||
|
color: #2f6f63;
|
||||||
|
font-size: .82rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-analysis-controls {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: .8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-analysis-controls label {
|
||||||
|
display: grid;
|
||||||
|
gap: .35rem;
|
||||||
|
min-width: 0;
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-analysis-controls select {
|
||||||
|
min-height: 3rem;
|
||||||
|
border-color: rgba(31, 42, 68, .22);
|
||||||
|
background-color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-analysis-flow {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: .65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-analysis-flow div {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
|
align-items: center;
|
||||||
|
gap: .55rem;
|
||||||
|
min-width: 0;
|
||||||
|
border: 1px solid rgba(31, 42, 68, .1);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: .7rem;
|
||||||
|
background: rgba(255, 255, 255, .64);
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-analysis-flow span {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 1.45rem;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #2f6f63;
|
||||||
|
color: #fff;
|
||||||
|
font-size: .8rem;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-analysis-flow strong {
|
||||||
|
min-width: 0;
|
||||||
|
font-size: .9rem;
|
||||||
|
}
|
||||||
|
|
||||||
.story-intelligence-dashboard-card {
|
.story-intelligence-dashboard-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -1585,6 +1762,8 @@ summary.story-review-chapter-heading {
|
|||||||
.onboarding-dashboard-nudge,
|
.onboarding-dashboard-nudge,
|
||||||
.onboarding-companion-card,
|
.onboarding-companion-card,
|
||||||
.onboarding-review-grid,
|
.onboarding-review-grid,
|
||||||
|
.onboarding-analysis-controls,
|
||||||
|
.onboarding-analysis-flow,
|
||||||
.onboarding-summary div,
|
.onboarding-summary div,
|
||||||
.story-intelligence-dashboard-card {
|
.story-intelligence-dashboard-card {
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
@ -1592,6 +1771,15 @@ summary.story-review-chapter-heading {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.onboarding-analysis-setup__header,
|
||||||
|
.onboarding-review-section-heading {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-review-tabs {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
.onboarding-story-facts {
|
.onboarding-story-facts {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user