From bfe729e8c04c5ebafeb8822976fa6f150d1a2e3d Mon Sep 17 00:00:00 2001 From: Nick Beckley Date: Thu, 13 Aug 2026 19:03:36 +0000 Subject: [PATCH] Make onboarding scan review a linear wizard --- PlotLine/Controllers/OnboardingController.cs | 13 +- PlotLine/Services/OnboardingService.cs | 88 ++-- .../OnboardingStoryIntelligenceService.cs | 7 + PlotLine/ViewModels/OnboardingViewModels.cs | 4 +- PlotLine/Views/Onboarding/Index.cshtml | 61 +-- PlotLine/Views/Onboarding/ScanReview.cshtml | 399 +++++++----------- .../Views/Onboarding/StoryIntelligence.cshtml | 4 +- .../_OnboardingJourneyHeader.cshtml | 63 ++- PlotLine/wwwroot/css/onboarding.css | 44 +- 9 files changed, 329 insertions(+), 354 deletions(-) diff --git a/PlotLine/Controllers/OnboardingController.cs b/PlotLine/Controllers/OnboardingController.cs index 25c8fb3..1a4f802 100644 --- a/PlotLine/Controllers/OnboardingController.cs +++ b/PlotLine/Controllers/OnboardingController.cs @@ -49,7 +49,7 @@ public sealed class OnboardingController(IOnboardingService onboarding, IOnboard } [HttpGet("scan-characters")] - public async Task ScanCharacters(Guid? previewId) + public async Task ScanCharacters(Guid? previewId, int page = 1) { var model = await onboarding.GetScanReviewAsync(previewId) ?? (previewId.HasValue ? await onboarding.GetScanReviewAsync() : null); @@ -61,6 +61,7 @@ public sealed class OnboardingController(IOnboardingService onboarding, IOnboard } ViewData["ScanReviewStep"] = "characters"; + ViewData["CharacterReviewPage"] = Math.Max(1, page); return View("ScanReview", model); } @@ -513,10 +514,13 @@ public sealed class OnboardingController(IOnboardingService onboarding, IOnboard [RequestSizeLimit(25 * 1024 * 1024)] [RequestFormLimits(ValueCountLimit = 20000, KeyLengthLimit = 4096, ValueLengthLimit = 1024 * 1024)] [ValidateAntiForgeryToken] - public async Task SaveScanReview(ManuscriptScanReviewForm form, string intent = "save") + public async Task SaveScanReview(ManuscriptScanReviewForm form, string intent = "save", int characterPage = 1) { var continueToCharacters = string.Equals(intent, "characters", StringComparison.OrdinalIgnoreCase); + var continueCharacterReview = string.Equals(intent, "character-next", StringComparison.OrdinalIgnoreCase); + var backCharacterReview = string.Equals(intent, "character-back", StringComparison.OrdinalIgnoreCase); var readyToImport = string.Equals(intent, "continue", StringComparison.OrdinalIgnoreCase); + var nextCharacterPage = Math.Max(1, characterPage + (backCharacterReview ? -1 : 1)); try { var model = await onboarding.SaveScanReviewAsync(form, readyToImport); @@ -528,9 +532,11 @@ public sealed class OnboardingController(IOnboardingService onboarding, IOnboard TempData["OnboardingReviewMessage"] = readyToImport ? "Review saved. Next: prepare chapters and analyse manuscript." : continueToCharacters ? "Chapter and scene review saved. Next: review detected characters." + : continueCharacterReview || backCharacterReview ? "Character review choices saved." : "Review choices saved."; return readyToImport ? RedirectToAction(nameof(StoryIntelligence)) + : continueCharacterReview || backCharacterReview ? RedirectToAction(nameof(ScanCharacters), new { previewId = form.PreviewID, page = nextCharacterPage }) : continueToCharacters ? RedirectToAction(nameof(ScanCharacters), new { previewId = form.PreviewID }) : RedirectToAction(nameof(ScanReview), new { previewId = form.PreviewID }); } @@ -543,9 +549,10 @@ public sealed class OnboardingController(IOnboardingService onboarding, IOnboard return NotFound(); } - if (readyToImport) + if (readyToImport || continueCharacterReview || backCharacterReview) { ViewData["ScanReviewStep"] = "characters"; + ViewData["CharacterReviewPage"] = Math.Max(1, characterPage); } return View("ScanReview", model); diff --git a/PlotLine/Services/OnboardingService.cs b/PlotLine/Services/OnboardingService.cs index c8007ab..d15dda0 100644 --- a/PlotLine/Services/OnboardingService.cs +++ b/PlotLine/Services/OnboardingService.cs @@ -188,13 +188,30 @@ public sealed class OnboardingService( var review = new ManuscriptScanReviewDecision { PreviewID = preview.PreviewID, - Status = readyToImport ? ManuscriptScanReviewStatuses.ReadyToImport : ManuscriptScanReviewStatuses.ReviewInProgress, + Status = ManuscriptScanReviewStatuses.ReviewInProgress, Chapters = chapterDecisions, Scenes = sceneDecisions, Characters = characterDecisions }; - ValidateScanReview(review, preview); + if (readyToImport) + { + await scanPreviews.SaveReviewAsync(userId, review); + ValidateScanReview(review, preview); + review = new ManuscriptScanReviewDecision + { + PreviewID = review.PreviewID, + Status = ManuscriptScanReviewStatuses.ReadyToImport, + Chapters = review.Chapters, + Scenes = review.Scenes, + Characters = review.Characters + }; + } + else + { + ValidateScanStructure(review, preview); + } + var saved = await scanPreviews.SaveReviewAsync(userId, review); return saved is null ? null : BuildScanReviewViewModel((userId, preview, saved, project, book, existingCharacters)); } @@ -439,6 +456,42 @@ public sealed class OnboardingService( } private static void ValidateScanReview(ManuscriptScanReviewDecision review, ManuscriptScanPreview preview) + { + ValidateScanStructure(review, preview); + + var duplicateName = review.Characters + .Where(IsIncludedCanonicalCharacter) + .Select(character => Clean(character.Name)) + .Where(name => !string.IsNullOrWhiteSpace(name)) + .GroupBy(name => name, StringComparer.OrdinalIgnoreCase) + .FirstOrDefault(group => group.Count() > 1); + if (duplicateName is not null) + { + throw new InvalidOperationException($"The character name \"{duplicateName.Key}\" appears more than once. Exclude or rename one before continuing."); + } + + if (review.Characters.Any(character => character.Include && string.IsNullOrWhiteSpace(character.Name))) + { + throw new InvalidOperationException("Every included character needs a display name."); + } + + var characterLookup = review.Characters.ToDictionary(character => character.TemporaryCharacterKey, StringComparer.Ordinal); + foreach (var character in review.Characters.Where(character => character.Include && !string.IsNullOrWhiteSpace(character.AliasTargetTemporaryCharacterKey))) + { + if (string.Equals(character.TemporaryCharacterKey, character.AliasTargetTemporaryCharacterKey, StringComparison.Ordinal)) + { + throw new InvalidOperationException("A character candidate cannot be an alias of itself."); + } + + if (!characterLookup.TryGetValue(character.AliasTargetTemporaryCharacterKey!, out var target) + || !IsIncludedCanonicalCharacter(target)) + { + throw new InvalidOperationException($"Choose an included primary character for the alias \"{character.Name}\"."); + } + } + } + + private static void ValidateScanStructure(ManuscriptScanReviewDecision review, ManuscriptScanPreview preview) { var previewChapters = preview.Chapters.ToDictionary(chapter => chapter.TemporaryChapterKey, StringComparer.Ordinal); var previewScenes = preview.Scenes.ToDictionary(scene => scene.TemporarySceneKey, StringComparer.Ordinal); @@ -488,37 +541,6 @@ public sealed class OnboardingService( throw new InvalidOperationException("One of the selected chapters no longer exists in the scan preview."); } } - - var duplicateName = review.Characters - .Where(IsIncludedCanonicalCharacter) - .Select(character => Clean(character.Name)) - .Where(name => !string.IsNullOrWhiteSpace(name)) - .GroupBy(name => name, StringComparer.OrdinalIgnoreCase) - .FirstOrDefault(group => group.Count() > 1); - if (duplicateName is not null) - { - throw new InvalidOperationException($"The character name \"{duplicateName.Key}\" appears more than once. Exclude or rename one before continuing."); - } - - if (review.Characters.Any(character => character.Include && string.IsNullOrWhiteSpace(character.Name))) - { - throw new InvalidOperationException("Every included character needs a display name."); - } - - var characterLookup = review.Characters.ToDictionary(character => character.TemporaryCharacterKey, StringComparer.Ordinal); - foreach (var character in review.Characters.Where(character => character.Include && !string.IsNullOrWhiteSpace(character.AliasTargetTemporaryCharacterKey))) - { - if (string.Equals(character.TemporaryCharacterKey, character.AliasTargetTemporaryCharacterKey, StringComparison.Ordinal)) - { - throw new InvalidOperationException("A character candidate cannot be an alias of itself."); - } - - if (!characterLookup.TryGetValue(character.AliasTargetTemporaryCharacterKey!, out var target) - || !IsIncludedCanonicalCharacter(target)) - { - throw new InvalidOperationException($"Choose an included primary character for the alias \"{character.Name}\"."); - } - } } private async Task AddScanCharacterAliasesAsync(ManuscriptScanReviewDecision review, OnboardingManuscriptBuildResult result) diff --git a/PlotLine/Services/OnboardingStoryIntelligenceService.cs b/PlotLine/Services/OnboardingStoryIntelligenceService.cs index 89fa528..40cbc0b 100644 --- a/PlotLine/Services/OnboardingStoryIntelligenceService.cs +++ b/PlotLine/Services/OnboardingStoryIntelligenceService.cs @@ -66,6 +66,11 @@ public sealed class OnboardingStoryIntelligenceService( var included = IncludedChapters(preview, review); var existingBatch = await batchStore.GetLatestForPreviewAsync(userId, preview.PreviewID); var missingTextCount = included.Count(chapter => string.IsNullOrWhiteSpace(chapter.Preview.ChapterText)); + var primaryCharacterCount = preview.CharacterCandidates.Count(candidate => !string.Equals(candidate.Category, "Excluded", StringComparison.OrdinalIgnoreCase)); + var excludedCharacterCount = preview.CharacterCandidates.Count(candidate => string.Equals(candidate.Category, "Excluded", StringComparison.OrdinalIgnoreCase)); + var characterPageCount = Math.Max(1, (int)Math.Ceiling(primaryCharacterCount / 10d)) + + (excludedCharacterCount > 0 ? (int)Math.Ceiling(excludedCharacterCount / 10d) : 0); + var totalWizardSteps = 7 + characterPageCount + 2; return new StoryIntelligenceOverviewViewModel { PreviewID = preview.PreviewID, @@ -76,6 +81,8 @@ public sealed class OnboardingStoryIntelligenceService( ApprovedChapterCount = included.Count, ApprovedWordCount = included.Sum(chapter => chapter.Preview.WordCount), MissingChapterTextCount = missingTextCount, + WizardCurrentStep = totalWizardSteps - 1, + WizardTotalSteps = totalWizardSteps, ExistingBatchID = existingBatch?.BatchID, CanStart = included.Count > 0 && missingTextCount == 0, Message = build is null diff --git a/PlotLine/ViewModels/OnboardingViewModels.cs b/PlotLine/ViewModels/OnboardingViewModels.cs index 189be77..9d943d9 100644 --- a/PlotLine/ViewModels/OnboardingViewModels.cs +++ b/PlotLine/ViewModels/OnboardingViewModels.cs @@ -36,7 +36,7 @@ public sealed class OnboardingOptionViewModel public sealed class OnboardingJourneyHeaderViewModel { public int CurrentStep { get; init; } - public int TotalSteps { get; init; } = 9; + public int TotalSteps { get; init; } = 10; public string AriaLabel { get; init; } = "Manuscript import progress"; } @@ -127,6 +127,8 @@ public sealed class StoryIntelligenceOverviewViewModel public int ApprovedChapterCount { get; init; } public int ApprovedWordCount { get; init; } public int MissingChapterTextCount { get; init; } + public int WizardCurrentStep { get; init; } = 9; + public int WizardTotalSteps { get; init; } = 10; public string? Message { get; init; } public Guid? ExistingBatchID { get; init; } public StoryIntelligenceJobProgress? ExistingJob { get; init; } diff --git a/PlotLine/Views/Onboarding/Index.cshtml b/PlotLine/Views/Onboarding/Index.cshtml index 873a3c2..e8e525e 100644 --- a/PlotLine/Views/Onboarding/Index.cshtml +++ b/PlotLine/Views/Onboarding/Index.cshtml @@ -233,7 +233,7 @@ @if (Model.IsMicrosoftWordPath) {

@(currentJourneyOrder >= 6 ? "Scan Manuscript" : "Connect your Word Companion")

-

Open Microsoft Word, open your manuscript, start the PlotDirector Word Companion, and sign in using your PlotDirector account. PlotDirector will stay on this page with you while the scan runs.

+

Open your manuscript in Word and start the Companion. When it is connected, use the wizard button below to scan and continue.

} else { @@ -243,7 +243,7 @@ @if (Model.IsMicrosoftWordPath) { -
@@ -296,20 +296,6 @@ @Model.ScanState.TotalWordCount.ToString("N0") - @if (string.Equals(Model.ScanState.ReviewStatus, ManuscriptScanReviewStatuses.ReadyToImport, StringComparison.Ordinal)) {
@@ -322,51 +308,40 @@
- - Analyse manuscript - }
} -
-
-
Writing journey
-
@DisplayJourney(Model.WritingJourney)
-
-
-
Writing software
-
@DisplaySoftware(Model.WritingSoftware)
-
-
-
Project
-
@(Model.SelectedProjectName ?? "Not set")
-
-
-
Book
-
@(Model.SelectedBookTitle ?? "Not set")
-
-
- @if (Model.ProjectID.HasValue) - { - Go to Project Overview - } @if (Model.IsMicrosoftWordPath && string.Equals(Model.ScanState.ReviewStatus, ManuscriptScanReviewStatuses.ReadyToImport, StringComparison.Ordinal)) { Analyse manuscript } else if (Model.IsMicrosoftWordPath && Model.ScanState.IsComplete) { - Review the scan above to continue. + + Review chapters + } else if (Model.IsMicrosoftWordPath) { - Scan your manuscript to continue. + + + Review chapters + } else { diff --git a/PlotLine/Views/Onboarding/ScanReview.cshtml b/PlotLine/Views/Onboarding/ScanReview.cshtml index 3a9f935..a6c8544 100644 --- a/PlotLine/Views/Onboarding/ScanReview.cshtml +++ b/PlotLine/Views/Onboarding/ScanReview.cshtml @@ -13,32 +13,60 @@ var excludedCharacters = Model.CharacterCandidates .Where(candidate => string.Equals(candidate.Category, "Excluded", StringComparison.OrdinalIgnoreCase)) .ToList(); + var primaryCharacters = probableCharacters + .Concat(possibleCharacters) + .Concat(relationshipTitles) + .ToList(); var detectedAliasTargets = Model.CharacterCandidates .Where(candidate => !string.Equals(candidate.Category, "Excluded", StringComparison.OrdinalIgnoreCase)) .OrderBy(candidate => candidate.ReviewName) .ToList(); var visibleCharacterCount = probableCharacters.Count + possibleCharacters.Count + relationshipTitles.Count; var isCharacterStep = string.Equals(ViewData["ScanReviewStep"] as string, "characters", StringComparison.OrdinalIgnoreCase); - var currentStep = isCharacterStep ? 8 : 7; + const int characterPageSize = 10; + var primaryCharacterPageCount = Math.Max(1, (int)Math.Ceiling(primaryCharacters.Count / (double)characterPageSize)); + var excludedCharacterPageCount = excludedCharacters.Any() + ? (int)Math.Ceiling(excludedCharacters.Count / (double)characterPageSize) + : 0; + var characterReviewPageCount = primaryCharacterPageCount + excludedCharacterPageCount; + var requestedCharacterPage = ViewData["CharacterReviewPage"] is int requestedPage ? requestedPage : 1; + var characterPage = Math.Clamp(requestedCharacterPage, 1, characterReviewPageCount); + var isExcludedCharacterPage = excludedCharacterPageCount > 0 && characterPage > primaryCharacterPageCount; + var excludedPageIndex = isExcludedCharacterPage ? characterPage - primaryCharacterPageCount : 0; + var currentCharacters = isExcludedCharacterPage + ? excludedCharacters.Skip((excludedPageIndex - 1) * characterPageSize).Take(characterPageSize).ToList() + : primaryCharacters.Skip((characterPage - 1) * characterPageSize).Take(characterPageSize).ToList(); + var currentStep = isCharacterStep ? 7 + characterPage : 7; + var totalSteps = 7 + characterReviewPageCount + 2; + var characterPageTitle = isExcludedCharacterPage + ? "Review Excluded Characters" + : $"Review Characters {characterPage} of {primaryCharacterPageCount}"; }
@Html.AntiForgeryToken() - + +
-

Step @currentStep of 10

-

@(isCharacterStep ? "Review Characters" : "Review Chapters")

-

@(isCharacterStep ? "Review detected character names, aliases and existing character matches before Story Intelligence begins." : "Confirm the chapter and scene structure before reviewing detected characters.")

+

Step @currentStep of @totalSteps

+

@(isCharacterStep ? characterPageTitle : "Review Chapters")

+

@(isCharacterStep ? "Review this page of detected names and aliases, then continue through the wizard." : "Confirm the chapter and scene structure before reviewing detected characters.")

@if (TempData["OnboardingReviewMessage"] is string reviewMessage) {
@reviewMessage
} -
+ @if (!ViewData.ModelState.IsValid) + { + + }
@@ -157,153 +185,124 @@
+ } +
+ @foreach (var candidate in allCharacters.Where(candidate => !currentCharacters.Any(current => string.Equals(current.TemporaryCharacterKey, candidate.TemporaryCharacterKey, StringComparison.Ordinal)))) + { + var hiddenCharacterIndex = allCharacters.FindIndex(item => string.Equals(item.TemporaryCharacterKey, candidate.TemporaryCharacterKey, StringComparison.Ordinal)); + } @@ -311,13 +310,29 @@
- Back to setup @if (isCharacterStep) { - + @if (characterPage <= 1) + { + Back to chapters + } + else + { + + } + + @if (characterPage < characterReviewPageCount) + { + + } + else + { + + } } else { + Back to setup }
@@ -338,90 +353,4 @@ checkbox.checked = checked; }); }); - - const characterFilter = document.querySelector("[data-character-filter]"); - const characterPager = document.querySelector("[data-character-pager]"); - const characterPageLabel = document.querySelector("[data-character-page-label]"); - const characterPagePrev = document.querySelector("[data-character-page-prev]"); - const characterPageNext = document.querySelector("[data-character-page-next]"); - const characterPageSize = 20; - let characterPage = 1; - - const characterItems = () => [...document.querySelectorAll("[data-character-page-item]")]; - const excludedCharacterItems = () => [...document.querySelectorAll("[data-character-excluded-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) => { - const hasVisibleItems = [...section.querySelectorAll("[data-character-page-item]")].some((item) => !item.hidden); - if (!section.closest(".onboarding-excluded-candidates")) { - section.hidden = !hasVisibleItems; - } - }); - }; - - const renderCharacterPage = () => { - const query = characterFilter?.value.trim().toLowerCase() || ""; - 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(); - excludedCharacterItems().forEach((item) => { - const text = `${item.dataset.characterName || ""} ${item.dataset.characterReason || ""}`.toLowerCase(); - item.hidden = query.length > 0 && !text.includes(query); - }); - - const emptyState = document.querySelector("[data-character-empty]"); - if (emptyState) { - emptyState.hidden = matches.length > 0 || excludedCharacterItems().some((item) => !item.hidden); - } - }; - - characterFilter?.addEventListener("input", () => { - characterPage = 1; - renderCharacterPage(); - }); - - characterPagePrev?.addEventListener("click", () => { - characterPage -= 1; - renderCharacterPage(); - }); - - characterPageNext?.addEventListener("click", () => { - characterPage += 1; - renderCharacterPage(); - }); - - renderCharacterPage(); diff --git a/PlotLine/Views/Onboarding/StoryIntelligence.cshtml b/PlotLine/Views/Onboarding/StoryIntelligence.cshtml index a4d7621..ee8ba34 100644 --- a/PlotLine/Views/Onboarding/StoryIntelligence.cshtml +++ b/PlotLine/Views/Onboarding/StoryIntelligence.cshtml @@ -5,9 +5,9 @@
- +
-

Step 9 of 10

+

Step @Model.WizardCurrentStep of @Model.WizardTotalSteps

Analyse Manuscript

Set the analysis context, then PlotDirector will read the approved chapters and prepare scenes, characters, locations, assets, relationships and knowledge for review.

diff --git a/PlotLine/Views/Onboarding/_OnboardingJourneyHeader.cshtml b/PlotLine/Views/Onboarding/_OnboardingJourneyHeader.cshtml index 1acb40c..c95219f 100644 --- a/PlotLine/Views/Onboarding/_OnboardingJourneyHeader.cshtml +++ b/PlotLine/Views/Onboarding/_OnboardingJourneyHeader.cshtml @@ -1,27 +1,21 @@ @model OnboardingJourneyHeaderViewModel @{ - var steps = new[] - { - new { Label = "Welcome", Order = 1 }, - new { Label = "Writing Stage", Order = 2 }, - new { Label = "Writing Software", Order = 3 }, - new { Label = "Project", Order = 4 }, - new { Label = "Book", Order = 5 }, - new { Label = "Connect Word", Order = 6 }, - new { Label = "Review Chapters", Order = 7 }, - new { Label = "Review Characters", Order = 8 }, - new { Label = "Analyse Manuscript", Order = 9 }, - new { Label = "Complete", Order = 10 } - }; + var characterReviewStepCount = Math.Max(1, Model.TotalSteps - 9); + var steps = Model.TotalSteps <= 10 + ? BuildDefaultSteps() + : BuildDynamicSteps(characterReviewStepCount, Model.TotalSteps); + var progressWidth = Model.TotalSteps <= 1 + ? 100 + : ((Math.Clamp(Model.CurrentStep, 1, Model.TotalSteps) - 1) * 100) / (Model.TotalSteps - 1); }
Step @Model.CurrentStep of @Model.TotalSteps
- +
-
    +
      @foreach (var step in steps) {
    1. @@ -32,6 +26,45 @@
    @functions { + private sealed record HeaderStep(string Label, int Order); + + private static IReadOnlyList BuildDefaultSteps() => + [ + new("Welcome", 1), + new("Writing Stage", 2), + new("Writing Software", 3), + new("Project", 4), + new("Book", 5), + new("Connect Word", 6), + new("Review Chapters", 7), + new("Review Characters", 8), + new("Analyse Manuscript", 9), + new("Complete", 10) + ]; + + private static IReadOnlyList BuildDynamicSteps(int characterReviewStepCount, int totalSteps) + { + var steps = new List + { + new("Welcome", 1), + new("Writing Stage", 2), + new("Writing Software", 3), + new("Project", 4), + new("Book", 5), + new("Connect Word", 6), + new("Review Chapters", 7) + }; + + for (var index = 1; index <= characterReviewStepCount; index++) + { + steps.Add(new HeaderStep($"Characters {index}", 7 + index)); + } + + steps.Add(new HeaderStep("Analyse Manuscript", totalSteps - 1)); + steps.Add(new HeaderStep("Complete", totalSteps)); + return steps; + } + private static string StepClass(int order, int currentStep) { if (order < currentStep) diff --git a/PlotLine/wwwroot/css/onboarding.css b/PlotLine/wwwroot/css/onboarding.css index 0520040..37535ef 100644 --- a/PlotLine/wwwroot/css/onboarding.css +++ b/PlotLine/wwwroot/css/onboarding.css @@ -38,7 +38,7 @@ .onboarding-stepper { display: grid; - grid-template-columns: repeat(10, minmax(0, 1fr)); + grid-template-columns: repeat(var(--onboarding-step-count, 10), minmax(0, 1fr)); gap: .45rem; margin: 0 0 2rem; padding: 0; @@ -268,6 +268,11 @@ background: linear-gradient(135deg, rgba(47, 111, 99, .1), rgba(182, 138, 70, .12)); } +.onboarding-companion-card--compact { + align-items: start; + margin-bottom: 1rem; +} + .onboarding-companion-mark { position: relative; width: 4.25rem; @@ -1319,36 +1324,31 @@ summary.story-review-chapter-heading { font-size: 1rem; } -.onboarding-character-pager { - display: flex; - align-items: center; - justify-content: space-between; - gap: .75rem; +.onboarding-character-step-summary { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: .65rem; +} + +.onboarding-character-step-summary div { + min-width: 0; border: 1px solid rgba(31, 42, 68, .1); border-radius: 8px; - padding: .6rem; + padding: .7rem; background: rgba(255, 255, 255, .62); } -.onboarding-character-pager span { +.onboarding-character-step-summary span { + display: block; color: var(--bs-secondary-color); + font-size: .76rem; font-weight: 800; - text-align: center; + text-transform: uppercase; } -.onboarding-excluded-candidates { - border-top: 1px solid rgba(31, 42, 68, .12); - padding-top: .65rem; -} - -.onboarding-excluded-candidates summary { - cursor: pointer; - color: var(--bs-secondary-color); - font-weight: 700; -} - -.onboarding-excluded-candidates .onboarding-character-candidates { - margin-top: .65rem; +.onboarding-character-step-summary strong { + display: block; + margin-top: .15rem; } .onboarding-summary div {