Keep character review within one wizard step
This commit is contained in:
parent
1ead298fa8
commit
1c7cf9bee1
@ -67,7 +67,9 @@ var tests = new (string Name, Action Test)[]
|
|||||||
("Character sex hints are conservative and reviewable", CharacterSexHintsAreConservativeAndReviewable),
|
("Character sex hints are conservative and reviewable", CharacterSexHintsAreConservativeAndReviewable),
|
||||||
("Character discovery does not truncate scan review candidates", CharacterDiscoveryDoesNotTruncateScanReviewCandidates),
|
("Character discovery does not truncate scan review candidates", CharacterDiscoveryDoesNotTruncateScanReviewCandidates),
|
||||||
("Character discovery rejects sentence-start words", CharacterDiscoveryRejectsSentenceStartWords),
|
("Character discovery rejects sentence-start words", CharacterDiscoveryRejectsSentenceStartWords),
|
||||||
|
("Character discovery rejects fragments and location names", CharacterDiscoveryRejectsFragmentsAndLocationNames),
|
||||||
("Manuscript scan preview keeps only auto-selected character candidates", ManuscriptScanPreviewKeepsOnlyAutoSelectedCharacterCandidates),
|
("Manuscript scan preview keeps only auto-selected character candidates", ManuscriptScanPreviewKeepsOnlyAutoSelectedCharacterCandidates),
|
||||||
|
("Onboarding wizard keeps character pages inside one main step", OnboardingWizardKeepsCharacterPagesInsideOneMainStep),
|
||||||
("Scan review aliases feed Story Intelligence known character context", ScanReviewAliasesFeedStoryIntelligenceKnownCharacterContext),
|
("Scan review aliases feed Story Intelligence known character context", ScanReviewAliasesFeedStoryIntelligenceKnownCharacterContext),
|
||||||
("Scan review post supports full-book form submissions", ScanReviewPostSupportsFullBookFormSubmissions),
|
("Scan review post supports full-book form submissions", ScanReviewPostSupportsFullBookFormSubmissions),
|
||||||
("Story Intelligence experience boot does not serialise live model", StoryIntelligenceExperienceBootDoesNotSerialiseLiveModel),
|
("Story Intelligence experience boot does not serialise live model", StoryIntelligenceExperienceBootDoesNotSerialiseLiveModel),
|
||||||
@ -1333,6 +1335,38 @@ static void ManuscriptScanPreviewKeepsOnlyAutoSelectedCharacterCandidates()
|
|||||||
Assert(candidates[0].Name == "Colin", "Scan preview should keep probable character candidates.");
|
Assert(candidates[0].Name == "Colin", "Scan preview should keep probable character candidates.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void CharacterDiscoveryRejectsFragmentsAndLocationNames()
|
||||||
|
{
|
||||||
|
var method = typeof(WordCompanionService).GetMethod("DiscoverCharacterCandidates", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
|
||||||
|
Assert(method is not null, "Character discovery method could not be found.");
|
||||||
|
|
||||||
|
var text = """
|
||||||
|
Bark Narrated the line break. Freedom Narrated another section.
|
||||||
|
Eventually Rob arrived and Rob spoke to Maggie.
|
||||||
|
Bristol Road curved away from Barmouth. Bristol Road appeared again.
|
||||||
|
Maggie said hello. Maggie smiled at Rob.
|
||||||
|
""";
|
||||||
|
|
||||||
|
var candidates = (IReadOnlyList<WordCompanionCharacterCandidateDto>)method!.Invoke(null, [text, false])!;
|
||||||
|
Assert(candidates.Any(candidate => candidate.Text == "Maggie"), "Real character names should still survive fragment filtering.");
|
||||||
|
Assert(candidates.Any(candidate => candidate.Text == "Rob"), "Known character names should still survive fragment filtering.");
|
||||||
|
Assert(!candidates.Any(candidate => candidate.Text is "Bark Narrated" or "Freedom Narrated" or "Eventually Rob"), "Narrative fragments must not become character candidates.");
|
||||||
|
Assert(!candidates.Any(candidate => candidate.Text == "Bristol Road"), "Location names must not become character candidates.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnboardingWizardKeepsCharacterPagesInsideOneMainStep()
|
||||||
|
{
|
||||||
|
var header = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine/Views/Onboarding/_OnboardingJourneyHeader.cshtml"));
|
||||||
|
var review = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine/Views/Onboarding/ScanReview.cshtml"));
|
||||||
|
var storyService = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine/Services/OnboardingStoryIntelligenceService.cs"));
|
||||||
|
|
||||||
|
Assert(!header.Contains("BuildDynamicSteps", StringComparison.Ordinal), "Onboarding header should not create one top-level step per character page.");
|
||||||
|
Assert(header.Contains("new(\"Review Characters\", 8)", StringComparison.Ordinal), "Onboarding header should keep character review as one main step.");
|
||||||
|
Assert(review.Contains("var currentStep = isCharacterStep ? 8 : 7;", StringComparison.Ordinal), "Scan review should keep every character page on the same main wizard step.");
|
||||||
|
Assert(review.Contains("onboarding-subprogress", StringComparison.Ordinal), "Character pagination should be shown as section-level progress.");
|
||||||
|
Assert(storyService.Contains("const int totalWizardSteps = 11;", StringComparison.Ordinal), "Story Intelligence summary/context should use the fixed wizard step count.");
|
||||||
|
}
|
||||||
|
|
||||||
static void StoryIntelligenceExperienceBootDoesNotSerialiseLiveModel()
|
static void StoryIntelligenceExperienceBootDoesNotSerialiseLiveModel()
|
||||||
{
|
{
|
||||||
var view = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine/Views/Development/StoryIntelligenceExperience.cshtml"));
|
var view = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine/Views/Development/StoryIntelligenceExperience.cshtml"));
|
||||||
|
|||||||
@ -80,7 +80,7 @@ public sealed class OnboardingService(
|
|||||||
{
|
{
|
||||||
CurrentStep = step,
|
CurrentStep = step,
|
||||||
StepNumber = StepNumber(step),
|
StepNumber = StepNumber(step),
|
||||||
TotalSteps = 10,
|
TotalSteps = 11,
|
||||||
WritingJourney = state.WritingJourney,
|
WritingJourney = state.WritingJourney,
|
||||||
WritingSoftware = state.WritingSoftware,
|
WritingSoftware = state.WritingSoftware,
|
||||||
ProjectID = state.ProjectID,
|
ProjectID = state.ProjectID,
|
||||||
|
|||||||
@ -66,9 +66,9 @@ public sealed class OnboardingStoryIntelligenceService(
|
|||||||
var included = IncludedChapters(preview, review);
|
var included = IncludedChapters(preview, review);
|
||||||
var existingBatch = await batchStore.GetLatestForPreviewAsync(userId, preview.PreviewID);
|
var existingBatch = await batchStore.GetLatestForPreviewAsync(userId, preview.PreviewID);
|
||||||
var missingTextCount = included.Count(chapter => string.IsNullOrWhiteSpace(chapter.Preview.ChapterText));
|
var missingTextCount = included.Count(chapter => string.IsNullOrWhiteSpace(chapter.Preview.ChapterText));
|
||||||
var primaryCharacterCount = preview.CharacterCandidates.Count(candidate => !string.Equals(candidate.Category, "Excluded", StringComparison.OrdinalIgnoreCase));
|
const int storySummaryStep = 9;
|
||||||
var characterPageCount = Math.Max(1, (int)Math.Ceiling(primaryCharacterCount / 10d));
|
const int storyContextStep = 10;
|
||||||
var totalWizardSteps = 7 + characterPageCount + 3;
|
const int totalWizardSteps = 11;
|
||||||
return new StoryIntelligenceOverviewViewModel
|
return new StoryIntelligenceOverviewViewModel
|
||||||
{
|
{
|
||||||
PreviewID = preview.PreviewID,
|
PreviewID = preview.PreviewID,
|
||||||
@ -79,8 +79,8 @@ public sealed class OnboardingStoryIntelligenceService(
|
|||||||
ApprovedChapterCount = included.Count,
|
ApprovedChapterCount = included.Count,
|
||||||
ApprovedWordCount = included.Sum(chapter => chapter.Preview.WordCount),
|
ApprovedWordCount = included.Sum(chapter => chapter.Preview.WordCount),
|
||||||
MissingChapterTextCount = missingTextCount,
|
MissingChapterTextCount = missingTextCount,
|
||||||
WizardCurrentStep = totalWizardSteps - 2,
|
WizardCurrentStep = storySummaryStep,
|
||||||
WizardContextStep = totalWizardSteps - 1,
|
WizardContextStep = storyContextStep,
|
||||||
WizardTotalSteps = totalWizardSteps,
|
WizardTotalSteps = totalWizardSteps,
|
||||||
ExistingBatchID = existingBatch?.BatchID,
|
ExistingBatchID = existingBatch?.BatchID,
|
||||||
CanStart = included.Count > 0 && missingTextCount == 0,
|
CanStart = included.Count > 0 && missingTextCount == 0,
|
||||||
|
|||||||
@ -75,6 +75,10 @@ public sealed class WordCompanionService(
|
|||||||
"Course", "Thing", "Things", "Still", "Err", "Er", "Erm", "Alright", "Allright", "Alrite", "New",
|
"Course", "Thing", "Things", "Still", "Err", "Er", "Erm", "Alright", "Allright", "Alrite", "New",
|
||||||
"Somewhere", "Coke", "Like", "Don", "Isn"
|
"Somewhere", "Coke", "Like", "Don", "Isn"
|
||||||
};
|
};
|
||||||
|
private static readonly HashSet<string> CharacterDiscoveryFragmentWords = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
"Actually", "Eventually", "Finally", "Immediately", "Later", "Meanwhile", "Narrated", "Suddenly", "Then"
|
||||||
|
};
|
||||||
private static readonly HashSet<string> CharacterDiscoveryRelationshipTitles = new(StringComparer.OrdinalIgnoreCase)
|
private static readonly HashSet<string> CharacterDiscoveryRelationshipTitles = new(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
"Mum", "Dad", "Mother", "Father", "Gran", "Grandma", "Grandad", "Nan", "Nana", "Granny", "Brother",
|
"Mum", "Dad", "Mother", "Father", "Gran", "Grandma", "Grandad", "Nan", "Nana", "Granny", "Brother",
|
||||||
@ -693,6 +697,7 @@ public sealed class WordCompanionService(
|
|||||||
var hasTemporalWord = words.Any(word => CharacterDiscoveryTemporalWords.Contains(TrimHonorificPunctuation(word)));
|
var hasTemporalWord = words.Any(word => CharacterDiscoveryTemporalWords.Contains(TrimHonorificPunctuation(word)));
|
||||||
var hasLocationIndicator = words.Any(word => CharacterDiscoveryLocationIndicators.Contains(TrimHonorificPunctuation(word)));
|
var hasLocationIndicator = words.Any(word => CharacterDiscoveryLocationIndicators.Contains(TrimHonorificPunctuation(word)));
|
||||||
var hasStopWord = words.Any(word => CharacterDiscoveryStopWords.Contains(TrimHonorificPunctuation(word)));
|
var hasStopWord = words.Any(word => CharacterDiscoveryStopWords.Contains(TrimHonorificPunctuation(word)));
|
||||||
|
var hasFragmentWord = words.Any(word => CharacterDiscoveryFragmentWords.Contains(TrimHonorificPunctuation(word)));
|
||||||
var isRelationshipTitle = words.Length == 1 && CharacterDiscoveryRelationshipTitles.Contains(first);
|
var isRelationshipTitle = words.Length == 1 && CharacterDiscoveryRelationshipTitles.Contains(first);
|
||||||
var isSingleWord = words.Length == 1;
|
var isSingleWord = words.Length == 1;
|
||||||
var hasKnownGivenName = CharacterSexHintService.Suggest(first) != CharacterSexHintService.Unknown;
|
var hasKnownGivenName = CharacterSexHintService.Suggest(first) != CharacterSexHintService.Unknown;
|
||||||
@ -779,7 +784,7 @@ public sealed class WordCompanionService(
|
|||||||
score -= 3;
|
score -= 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
var category = CandidateCategory(score, hasStopWord, isRelationshipTitle, hasTemporalWord, hasLocationIndicator, evidence, isSingleWord, mostlySentenceStart, hasKnownGivenName || hasNameLikeWord, hasHonorific);
|
var category = CandidateCategory(score, hasStopWord || hasFragmentWord, isRelationshipTitle, hasTemporalWord, hasLocationIndicator, evidence, isSingleWord, mostlySentenceStart, hasKnownGivenName || hasNameLikeWord, hasHonorific);
|
||||||
var confidence = score >= 4 ? "High" : score >= 2 ? "Medium" : "Low";
|
var confidence = score >= 4 ? "High" : score >= 2 ? "Medium" : "Low";
|
||||||
if (hasLocationIndicator && evidence.DialogueEvidenceCount == 0 && !hasHonorific)
|
if (hasLocationIndicator && evidence.DialogueEvidenceCount == 0 && !hasHonorific)
|
||||||
{
|
{
|
||||||
@ -797,7 +802,7 @@ public sealed class WordCompanionService(
|
|||||||
Confidence = confidence,
|
Confidence = confidence,
|
||||||
QualityScore = score,
|
QualityScore = score,
|
||||||
Category = category,
|
Category = category,
|
||||||
Reason = BuildCandidateReason(evidence, hasStopWord, isRelationshipTitle, hasHonorific, hasMultiWordName, hasTemporalWord, hasLocationIndicator, mostlySentenceStart)
|
Reason = BuildCandidateReason(evidence, hasStopWord || hasFragmentWord, isRelationshipTitle, hasHonorific, hasMultiWordName, hasTemporalWord, hasLocationIndicator, mostlySentenceStart)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -842,6 +847,14 @@ public sealed class WordCompanionService(
|
|||||||
{
|
{
|
||||||
return "Excluded";
|
return "Excluded";
|
||||||
}
|
}
|
||||||
|
if (hasLocationIndicator
|
||||||
|
&& !hasHonorific
|
||||||
|
&& !hasNameEvidence
|
||||||
|
&& evidence.DialogueEvidenceCount == 0
|
||||||
|
&& evidence.PossessiveEvidenceCount == 0)
|
||||||
|
{
|
||||||
|
return "Excluded";
|
||||||
|
}
|
||||||
if (hasLocationIndicator && score < 4)
|
if (hasLocationIndicator && score < 4)
|
||||||
{
|
{
|
||||||
return "Excluded";
|
return "Excluded";
|
||||||
|
|||||||
@ -27,8 +27,8 @@
|
|||||||
var requestedCharacterPage = ViewData["CharacterReviewPage"] is int requestedPage ? requestedPage : 1;
|
var requestedCharacterPage = ViewData["CharacterReviewPage"] is int requestedPage ? requestedPage : 1;
|
||||||
var characterPage = Math.Clamp(requestedCharacterPage, 1, characterReviewPageCount);
|
var characterPage = Math.Clamp(requestedCharacterPage, 1, characterReviewPageCount);
|
||||||
var currentCharacters = primaryCharacters.Skip((characterPage - 1) * characterPageSize).Take(characterPageSize).ToList();
|
var currentCharacters = primaryCharacters.Skip((characterPage - 1) * characterPageSize).Take(characterPageSize).ToList();
|
||||||
var currentStep = isCharacterStep ? 7 + characterPage : 7;
|
var currentStep = isCharacterStep ? 8 : 7;
|
||||||
var totalSteps = 7 + characterReviewPageCount + 2;
|
const int totalSteps = 11;
|
||||||
var characterPageTitle = $"Review Characters {characterPage} of {primaryCharacterPageCount}";
|
var characterPageTitle = $"Review Characters {characterPage} of {primaryCharacterPageCount}";
|
||||||
var duplicateCanonicalNames = primaryCharacters
|
var duplicateCanonicalNames = primaryCharacters
|
||||||
.Where(candidate => candidate.Include && string.IsNullOrWhiteSpace(candidate.AliasTargetTemporaryCharacterKey))
|
.Where(candidate => candidate.Include && string.IsNullOrWhiteSpace(candidate.AliasTargetTemporaryCharacterKey))
|
||||||
@ -62,8 +62,8 @@
|
|||||||
|
|
||||||
<div class="onboarding-copy">
|
<div class="onboarding-copy">
|
||||||
<p class="eyebrow">Step @currentStep of @totalSteps</p>
|
<p class="eyebrow">Step @currentStep of @totalSteps</p>
|
||||||
<h1 id="scan-review-title">@(isCharacterStep ? characterPageTitle : "Review Chapters")</h1>
|
<h1 id="scan-review-title">@(isCharacterStep ? "Review Characters" : "Review Chapters")</h1>
|
||||||
<p>@(isCharacterStep ? "Review this page of detected names and aliases, then continue through the wizard." : "Confirm the chapter and scene structure before reviewing detected characters.")</p>
|
<p>@(isCharacterStep ? "Review this page of detected names and aliases, then continue through the character section." : "Confirm the chapter and scene structure before reviewing detected characters.")</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (TempData["OnboardingReviewMessage"] is string reviewMessage)
|
@if (TempData["OnboardingReviewMessage"] is string reviewMessage)
|
||||||
@ -193,6 +193,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside class="onboarding-review-section" data-scan-review-panel="characters" hidden="@(!isCharacterStep)">
|
<aside class="onboarding-review-section" data-scan-review-panel="characters" hidden="@(!isCharacterStep)">
|
||||||
|
<div class="onboarding-subprogress" aria-label="Character review progress">
|
||||||
|
<div>
|
||||||
|
<span>Character page @characterPage of @primaryCharacterPageCount</span>
|
||||||
|
<strong>@Math.Min(characterPage * characterPageSize, primaryCharacters.Count).ToString("N0") / @primaryCharacters.Count.ToString("N0") reviewed</strong>
|
||||||
|
</div>
|
||||||
|
<div class="onboarding-progress-track">
|
||||||
|
<span style="width:@((characterPage * 100) / primaryCharacterPageCount)%"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="onboarding-review-section-heading">
|
<div class="onboarding-review-section-heading">
|
||||||
<div>
|
<div>
|
||||||
<h2>@characterPageTitle</h2>
|
<h2>@characterPageTitle</h2>
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
@model OnboardingJourneyHeaderViewModel
|
@model OnboardingJourneyHeaderViewModel
|
||||||
@{
|
@{
|
||||||
var characterReviewStepCount = Math.Max(1, Model.TotalSteps - 10);
|
var steps = BuildDefaultSteps();
|
||||||
var steps = Model.TotalSteps <= 10
|
var totalSteps = Math.Max(Model.TotalSteps, steps.Count);
|
||||||
? BuildDefaultSteps()
|
var currentStep = Math.Clamp(Model.CurrentStep, 1, totalSteps);
|
||||||
: BuildDynamicSteps(characterReviewStepCount, Model.TotalSteps);
|
var progressWidth = totalSteps <= 1
|
||||||
var progressWidth = Model.TotalSteps <= 1
|
|
||||||
? 100
|
? 100
|
||||||
: ((Math.Clamp(Model.CurrentStep, 1, Model.TotalSteps) - 1) * 100) / (Model.TotalSteps - 1);
|
: ((currentStep - 1) * 100) / (totalSteps - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="onboarding-progress" aria-label="@Model.AriaLabel">
|
<div class="onboarding-progress" aria-label="@Model.AriaLabel">
|
||||||
<span>Step @Model.CurrentStep of @Model.TotalSteps</span>
|
<span>Step @currentStep of @totalSteps</span>
|
||||||
<div class="onboarding-progress-track">
|
<div class="onboarding-progress-track">
|
||||||
<span style="width:@progressWidth%"></span>
|
<span style="width:@progressWidth%"></span>
|
||||||
</div>
|
</div>
|
||||||
@ -18,7 +17,7 @@
|
|||||||
<ol class="onboarding-stepper" style="--onboarding-step-count:@steps.Count" aria-label="Manuscript import journey">
|
<ol class="onboarding-stepper" style="--onboarding-step-count:@steps.Count" aria-label="Manuscript import journey">
|
||||||
@foreach (var step in steps)
|
@foreach (var step in steps)
|
||||||
{
|
{
|
||||||
<li class="@StepClass(step.Order, Model.CurrentStep)">
|
<li class="@StepClass(step.Order, currentStep)">
|
||||||
<span>@step.Order</span>
|
<span>@step.Order</span>
|
||||||
<strong>@step.Label</strong>
|
<strong>@step.Label</strong>
|
||||||
</li>
|
</li>
|
||||||
@ -38,34 +37,11 @@
|
|||||||
new("Connect Word", 6),
|
new("Connect Word", 6),
|
||||||
new("Review Chapters", 7),
|
new("Review Chapters", 7),
|
||||||
new("Review Characters", 8),
|
new("Review Characters", 8),
|
||||||
new("Story Context", 9),
|
new("Story Summary", 9),
|
||||||
new("Complete", 10)
|
new("Story Context", 10),
|
||||||
|
new("Complete", 11)
|
||||||
];
|
];
|
||||||
|
|
||||||
private static IReadOnlyList<HeaderStep> BuildDynamicSteps(int characterReviewStepCount, int totalSteps)
|
|
||||||
{
|
|
||||||
var steps = new List<HeaderStep>
|
|
||||||
{
|
|
||||||
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("Story Summary", totalSteps - 2));
|
|
||||||
steps.Add(new HeaderStep("Story Context", totalSteps - 1));
|
|
||||||
steps.Add(new HeaderStep("Complete", totalSteps));
|
|
||||||
return steps;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string StepClass(int order, int currentStep)
|
private static string StepClass(int order, int currentStep)
|
||||||
{
|
{
|
||||||
if (order < currentStep)
|
if (order < currentStep)
|
||||||
|
|||||||
@ -514,6 +514,30 @@
|
|||||||
color: var(--bs-secondary-color);
|
color: var(--bs-secondary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.onboarding-subprogress {
|
||||||
|
display: grid;
|
||||||
|
gap: .55rem;
|
||||||
|
border: 1px solid rgba(47, 111, 99, .16);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: .8rem;
|
||||||
|
background: rgba(47, 111, 99, .06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-subprogress div:first-child {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
color: var(--bs-secondary-color);
|
||||||
|
font-size: .86rem;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-subprogress strong {
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.onboarding-character-filter {
|
.onboarding-character-filter {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: .35rem;
|
gap: .35rem;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user