Tighten header and character panel vertical density

This commit is contained in:
2026-02-26 12:10:24 +01:00
parent c3aa0d4e88
commit f9879c1541
6 changed files with 59 additions and 52 deletions

8
FAQ.md
View File

@@ -91,6 +91,14 @@ Skills now use inline row chip actions:
Roll visibility remains controlled in the skills header row.
## Why was the "Last Roll" card removed from the character panel?
The character column now prioritizes vertical density. Roll history is represented by the campaign log feed, so the dedicated last-roll card was removed to free space for skills and character context.
## Why does the character picker scroll horizontally?
The picker was intentionally compressed into a single compact row to minimize vertical real estate in the Play layout. When many characters exist, it scrolls horizontally instead of growing taller.
## Why is auth form state kept in `AuthSection` instead of `Home`?
Auth inputs, validation, and submit workflows are transient UI concerns, so they now live in `AuthSection`. `Home` keeps shared session/workspace state and cross-control refresh/orchestration only.

View File

@@ -11,9 +11,12 @@ Tracking against `UX.md` tasks and decisions.
- The authenticated workspace shell/state/behavior was moved to `Components/Pages/Workspace.razor`.
- Workspace header user identity rendering is now null-safe during first render (`Loading user...` fallback until `/api/me` loads).
- Workspace header was compacted into a single horizontal row with hamburger menu screen switching and link-style logout.
- Header alignment was tightened so connection status occupies the growing middle cell and the hamburger menu remains pinned to the right edge.
- Concern controls now own their local form state and mutation workflows; the workspace host handles shared cross-control state refresh.
- Skill create/edit flow is now owned by `CharacterPanel` (where characters and their skills are presented together).
- Skill interactions are now row-local chip actions (edit/roll) with an inline dummy `+` row for create-skill.
- Character picker was reduced to a compact single-row horizontal scroller to minimize vertical footprint.
- The standalone "Last Roll" panel was removed; campaign log entries are the roll history surface.
- Campaign log now auto-scrolls to the newest entry when new entries arrive.
## UX Checklist
@@ -22,8 +25,8 @@ Tracking against `UX.md` tasks and decisions.
|---|---|---|
| 9.1 App load + session restore | Implemented | Health check on load, rulesets/session load, unauthorized session reset, API unhealthy retry banner. |
| 9.2 Authentication view | Implemented | Register/login cards, required validation, register password length check, server-error display. |
| 9.3 Shared authenticated header | Implemented | Compact single-row header with user/campaign/connection context, hamburger menu screen switch, and link-style logout. |
| 9.4 Play screen character column | Implemented | Character icon tabs, merged character+skills header row, modal edit/create flows, inline per-skill edit/roll chips, d6 skill options (wild/fumble), and die-state visualized last roll card. |
| 9.3 Shared authenticated header | Implemented | Compact single-row header with user/campaign context, growing connection-status cell, right-aligned hamburger screen switch, and link-style logout. |
| 9.4 Play screen character column | Implemented | Compact character picker, merged character+skills header row, modal edit/create flows, inline per-skill edit/roll chips, d6 skill options (wild/fumble), and no separate last-roll panel. |
| 9.5 Play screen log column | Implemented | Chronological feed, private/public badges, private perspective styles (roller vs GM), per-entry dice visualization with die-state flags, local time + ISO tooltip. |
| 9.6 Campaign management screen | Implemented | Campaign selector/summary, create form, details card, character management actions with modal edit pattern. |
| 9.7 Tablet/mobile bottom bar | Implemented | `Character` / `Log` panel switch in play screen and per-tab session persistence. |

View File

@@ -32,6 +32,15 @@
{
<article class="skills-section">
<div class="section-head">
<button
type="button"
class="chip-button"
title="Edit character"
disabled="@(IsMutating || !CanEditCharacter(SelectedCharacter))"
@onclick="() => EditCharacterRequested.InvokeAsync(SelectedCharacter)">
<span aria-hidden="true">✎</span>
<span class="sr-only">Edit character</span>
</button>
<h3 class="skills-heading">@SelectedCharacter.Name <span
class="muted">| Owner: @OwnerLabel(SelectedCharacter.OwnerUserId) | Campaign: @SelectedCampaign.Name</span>
</h3>
@@ -41,15 +50,6 @@
<option value="public">Public</option>
<option value="private">Private</option>
</select>
<button
type="button"
class="chip-button"
title="Edit character"
disabled="@(IsMutating || !CanEditCharacter(SelectedCharacter))"
@onclick="() => EditCharacterRequested.InvokeAsync(SelectedCharacter)">
<span aria-hidden="true">✎</span>
<span class="sr-only">Edit character</span>
</button>
</div>
</div>
@if (SelectedCharacterSkills.Count == 0)
@@ -102,24 +102,6 @@
</article>
}
}
<article class="last-roll">
<h3>Last Roll</h3>
@if (LastRoll is null)
{
<p class="empty">No roll yet.</p>
}
else
{
<p class="roll-total">@LastRoll.Result</p>
<RollDiceStrip Dice="LastRoll.Dice" AriaLabel="Last roll dice"/>
<p>@LastRoll.Breakdown</p>
<p><span
class="badge @(LastRoll.Visibility == "private" ? "private-self" : "public")">@LastRoll.Visibility</span>
<time
title="@LastRoll.TimestampUtc.ToString("O")">@LastRoll.TimestampUtc.ToLocalTime().ToString("g")</time>
</p>
}
</article>
</section>
<SkillFormModal

View File

@@ -113,9 +113,6 @@ public partial class CharacterPanel
[Parameter]
public EventCallback<string> RollVisibilityChanged { get; set; }
[Parameter]
public RollResult? LastRoll { get; set; }
[Parameter]
public Func<Guid, string> OwnerLabel { get; set; } = _ => string.Empty;

View File

@@ -25,8 +25,11 @@
{
<p class="header-identity"><strong>@User.DisplayName</strong> <span class="muted">(@User.Username)</span></p>
}
<p class="connection @ConnectionStateCssClass">@ConnectionStateLabel</p>
<p class="header-campaign">Campaign: <strong>@(SelectedCampaignName ?? "No campaign selected")</strong></p>
<div class="header-connection-cell">
<p class="connection @ConnectionStateCssClass">@ConnectionStateLabel</p>
</div>
<a href="" class="logout-link" @onclick:preventDefault="true" @onclick="LogoutAsync">Logout</a>
<div class="header-menu-wrap">
<button
id="screen-menu-button"
@@ -37,7 +40,6 @@
aria-controls="screen-menu"
@onclick="ToggleScreenMenu">
<span aria-hidden="true">☰</span>
<span class="menu-toggle-label">@CurrentScreenLabel</span>
</button>
@if (IsScreenMenuOpen)
{
@@ -55,7 +57,6 @@
</div>
}
</div>
<a href="" class="logout-link" @onclick:preventDefault="true" @onclick="LogoutAsync">Logout</a>
</div>
</header>
@@ -77,7 +78,6 @@
IsD6="IsSelectedCampaignD6"
RollVisibility="RollVisibility"
RollVisibilityChanged="OnRollVisibilityChanged"
LastRoll="LastRoll"
OwnerLabel="OwnerLabel"
SkillDefinitionLabel="SkillDefinitionLabel"
CanEditCharacter="CanEditCharacter"

View File

@@ -92,9 +92,9 @@ h3 {
.header-row {
display: flex;
align-items: center;
gap: 0.6rem;
gap: 0.7rem;
width: 100%;
flex-wrap: wrap;
flex-wrap: nowrap;
}
.header-row h1 {
@@ -112,6 +112,13 @@ h3 {
color: var(--muted);
}
.header-connection-cell {
flex: 1 1 auto;
min-width: 0;
display: flex;
align-items: center;
}
.switch-group,
.header-actions,
.inline-actions {
@@ -247,16 +254,19 @@ select:focus-visible {
.character-picker {
display: flex;
flex-wrap: wrap;
gap: 0.45rem;
flex-wrap: nowrap;
gap: 0.3rem;
overflow-x: auto;
padding-bottom: 0.15rem;
}
.icon-tab {
display: grid;
place-items: center;
gap: 0.2rem;
min-width: 4.8rem;
padding: 0.45rem;
display: inline-flex;
align-items: center;
gap: 0.3rem;
min-width: 0;
padding: 0.22rem 0.45rem;
white-space: nowrap;
background: transparent;
color: var(--text);
border-color: #8e7b57;
@@ -268,21 +278,23 @@ select:focus-visible {
}
.icon-tab-glyph {
width: 2rem;
height: 2rem;
display: grid;
place-items: center;
width: 1.35rem;
height: 1.35rem;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
border: 1px solid #8e7b57;
font-weight: 700;
font-size: 0.72rem;
}
.icon-tab-text {
font-size: 0.82rem;
font-size: 0.76rem;
line-height: 1.1;
}
.skills-section,
.last-roll {
.skills-section {
border: 1px dashed #a89066;
border-radius: 0.65rem;
padding: 0.55rem;
@@ -377,6 +389,7 @@ select:focus-visible {
display: inline-flex;
align-items: center;
gap: 0.4rem;
margin-left: auto;
}
.visibility-control {
@@ -703,6 +716,10 @@ select:focus-visible {
position: static;
}
.header-row {
flex-wrap: wrap;
}
.play-screen {
grid-template-columns: 1fr;
}