refactor(split): extract DeepDrftShared.Client RCL with shared atoms

TrackCard, TracksGallery, DDIcons, DeepDrftPalettes (Default+Cms), DeepDrftFontLinks,
and palette CSS tokens extracted. Both hosts and DeepDrftCms reference the shared RCL.
This commit is contained in:
Daniel Harvey
2026-05-19 17:14:23 -04:00
parent 840192fb79
commit 8b8796fc58
22 changed files with 360 additions and 299 deletions
+25
View File
@@ -0,0 +1,25 @@
namespace DeepDrftShared.Client.Common;
public static class DDIcons
{
/// <summary>
/// Charleston gas lamp lantern - uses currentColor for theming
/// </summary>
public const string GasLamp = """
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="currentColor" d="M11 0h2v2h-2zM5 6l7-4 7 4v2H5zM6 8h12l-1.5 10h-9zM7.7 9l1.2 8h6.2l1.2-8zM9 19h6v1H9zM10 21h4v2h-4z"/>
</svg>
""";
/// <summary>
/// Charleston gas lamp with lit flame - for dark mode
/// </summary>
public const string GasLampLit = """
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="currentColor" d="M11 0h2v2h-2zM5 6l7-4 7 4v2H5zM6 8h12l-1.5 10h-9zM7.7 9l1.2 8h6.2l1.2-8zM9 19h6v1H9zM10 21h4v2h-4z"/>
<ellipse cx="12" cy="13" rx="2.5" ry="3.5" fill="#FF9800"/>
<ellipse cx="12" cy="12.5" rx="1.5" ry="2.5" fill="#FFCA28"/>
<ellipse cx="12" cy="12" rx=".7" ry="1.5" fill="#FFF8E1"/>
</svg>
""";
}
@@ -0,0 +1,152 @@
using MudBlazor;
namespace DeepDrftShared.Client.Common;
/// <summary>
/// Canonical MudBlazor theme for DeepDrft apps. Holds the wireframe light palette
/// (navy / green / warm off-white), the dark palette (navy ground / green-accent
/// / off-white), and the shared typography (Cormorant Garamond / Geist Mono / DM Sans).
///
/// <see cref="Default"/> is used by the public site (light+dark toggle).
/// <see cref="Cms"/> is used by the CMS host (light-only, solid navy AppBar for visual distinction).
/// Hosts decide light vs dark by toggling <c>IsDarkMode</c> on <c>MudThemeProvider</c>.
/// </summary>
// MudColor accepts string via implicit conversion, and MudTheme palette/typography
// properties are typed as nullable in MudBlazor. These assignments are valid but
// the nullable annotations trigger CS8601 as false positives.
#pragma warning disable CS8601
public static class DeepDrftPalettes
{
/// <summary>
/// The single MudTheme exposing both light and dark palettes plus typography.
/// Hosts pick a mode via <c>MudThemeProvider IsDarkMode</c>.
/// </summary>
public static MudTheme Default { get; } = new()
{
PaletteLight = Light,
PaletteDark = Dark,
Typography = Typography,
};
/// <summary>
/// CMS-specific MudTheme. Same as <see cref="Default"/> but with a solid navy AppBar
/// on the light palette to visually distinguish the admin surface from the public site.
/// </summary>
public static MudTheme Cms { get; } = new()
{
PaletteLight = CmsLight,
PaletteDark = Dark,
Typography = Typography,
};
// Wireframe light palette - navy / green / warm off-white
public static PaletteLight Light { get; } = new()
{
Primary = "#0D1B2A", // Navy - text, buttons
PrimaryDarken = "#162437", // Navy-mid - hover, elevated surfaces
Secondary = "#1A3C34", // Deep green - italic emphasis
Tertiary = "#3D7A68", // Green-accent - interactive hovers/active/icons
Background = "#FAFAF8", // Warm off-white
BackgroundGray = "#F0F2F0", // Slight warm gray for contrast
Surface = "#FAFAF8",
AppbarBackground = "rgba(250,250,248,0.88)",
AppbarText = "#0D1B2A",
TextPrimary = "#0D1B2A",
TextSecondary = "#8A9BB0", // Muted - secondary text, nav at rest
TextDisabled = "rgba(13,27,42,0.38)",
ActionDefault = "#8A9BB0",
ActionDisabled = "rgba(13,27,42,0.26)",
ActionDisabledBackground = "rgba(13,27,42,0.12)",
Divider = "rgba(13,27,42,0.10)",
DividerLight = "rgba(13,27,42,0.06)",
TableLines = "rgba(13,27,42,0.10)",
LinesDefault = "rgba(13,27,42,0.10)",
LinesInputs = "rgba(13,27,42,0.30)",
OverlayLight = "rgba(250,250,248,0.5)",
OverlayDark = "rgba(13,27,42,0.5)",
// Semantic (Info/Success/Warning/Error) intentionally left at MudBlazor defaults
};
// CMS light palette - same as Light but with a solid navy AppBar to distinguish
// the admin surface from the public site's semi-transparent off-white AppBar.
public static PaletteLight CmsLight { get; } = new()
{
Primary = Light.Primary,
PrimaryDarken = Light.PrimaryDarken,
Secondary = Light.Secondary,
Tertiary = Light.Tertiary,
Background = Light.Background,
BackgroundGray = Light.BackgroundGray,
Surface = Light.Surface,
AppbarBackground = "#0D1B2A", // Solid navy - CMS visual distinction
AppbarText = "#FAFAF8", // Off-white text on navy
TextPrimary = Light.TextPrimary,
TextSecondary = Light.TextSecondary,
TextDisabled = Light.TextDisabled,
ActionDefault = Light.ActionDefault,
ActionDisabled = Light.ActionDisabled,
ActionDisabledBackground = Light.ActionDisabledBackground,
Divider = Light.Divider,
DividerLight = Light.DividerLight,
TableLines = Light.TableLines,
LinesDefault = Light.LinesDefault,
LinesInputs = Light.LinesInputs,
OverlayLight = Light.OverlayLight,
OverlayDark = Light.OverlayDark,
// Semantic (Info/Success/Warning/Error) intentionally left at MudBlazor defaults
};
// Wireframe dark palette - navy ground / green-accent / off-white.
// Mirrors the light palette's vocabulary on a dark ground; the coral/lowcountry
// identity has been retired. On dark, green-accent (#3D7A68) becomes the primary
// interactive colour and off-white (#FAFAF8) becomes the secondary emphasis.
public static PaletteDark Dark { get; } = new()
{
Primary = "#3D7A68", // Green-accent - interactive colour on dark
PrimaryDarken = "#2A5C4F", // Green-light - hover/pressed
Secondary = "#FAFAF8", // Off-white - secondary emphasis
Tertiary = "#1A3C34", // Deep green - tertiary accent
Background = "#0D1B2A", // Navy - the light palette's primary as the dark ground
Surface = "#162437", // Navy-mid - elevated cards/panels
AppbarBackground = "rgba(13,27,42,0.92)", // Semi-opaque navy
AppbarText = "#FAFAF8",
DrawerBackground = "#162437", // Navy-mid
DrawerText = "#FAFAF8",
DrawerIcon = "#8A9BB0", // Muted
// TextPrimary (#FAFAF8) on Background (#0D1B2A): contrast ratio ~16.5:1 - passes WCAG AA (>=4.5:1)
TextPrimary = "#FAFAF8",
// TextSecondary (#8A9BB0) on Background (#0D1B2A): contrast ratio ~6.1:1 - passes WCAG AA for normal text
TextSecondary = "#8A9BB0",
TextDisabled = "rgba(250,250,248,0.38)",
ActionDefault = "#8A9BB0",
ActionDisabled = "rgba(250,250,248,0.26)",
ActionDisabledBackground = "rgba(250,250,248,0.12)",
Divider = "rgba(250,250,248,0.10)",
DividerLight = "rgba(250,250,248,0.06)",
TableLines = "rgba(250,250,248,0.10)",
LinesDefault = "rgba(250,250,248,0.10)",
LinesInputs = "rgba(250,250,248,0.30)",
OverlayLight = "rgba(13,27,42,0.5)",
OverlayDark = "rgba(0,0,0,0.7)",
// Semantic (Info/Success/Warning/Error) intentionally left at MudBlazor defaults
};
// Shared typography - Cormorant Garamond for display, Geist Mono for mono, DM Sans for body.
public static Typography Typography { get; } = new()
{
Default = new DefaultTypography { FontFamily = new[] { "DM Sans", "sans-serif" } },
H1 = new H1Typography { FontFamily = new[] { "Cormorant Garamond", "Georgia", "serif" } },
H2 = new H2Typography { FontFamily = new[] { "Cormorant Garamond", "Georgia", "serif" } },
H3 = new H3Typography { FontFamily = new[] { "Cormorant Garamond", "Georgia", "serif" } },
H4 = new H4Typography { FontFamily = new[] { "Cormorant Garamond", "Georgia", "serif" } },
// Extended to H5/H6 beyond spec - keeps heading hierarchy consistent in Cormorant Garamond
H5 = new H5Typography { FontFamily = new[] { "Cormorant Garamond", "Georgia", "serif" } },
H6 = new H6Typography { FontFamily = new[] { "Cormorant Garamond", "Georgia", "serif" } },
Subtitle1 = new Subtitle1Typography{ FontFamily = new[] { "Geist Mono", "monospace" } },
Body1 = new Body1Typography { FontFamily = new[] { "DM Sans", "sans-serif" } },
Body2 = new Body2Typography { FontFamily = new[] { "DM Sans", "sans-serif" } },
Caption = new CaptionTypography { FontFamily = new[] { "Geist Mono", "monospace" } },
Button = new ButtonTypography { FontFamily = new[] { "Geist Mono", "monospace" } },
};
}
#pragma warning restore CS8601
@@ -0,0 +1,6 @@
@* Google Fonts preconnects + stylesheet link for DeepDrft typography
(Cormorant Garamond / Geist Mono / DM Sans). Emitted once per HTML document
from each host's App.razor. *@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,600;1,300;1,400&family=Geist+Mono:wght@300;400;500&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;1,9..40,300&display=swap" rel="stylesheet">
@@ -0,0 +1,75 @@
<MudCard Class="deepdrft-track-card-container"
Elevation="4">
@if (!string.IsNullOrEmpty(TrackModel?.ImagePath))
{
<div class="deepdrft-track-card-bg" style="background-image: url('@TrackModel.ImagePath');">
</div>
}
else
{
<MudPaper Class="deepdrft-track-card-fallback mud-theme-secondary"
Elevation="0">
</MudPaper>
}
<MudCardContent Class="deepdrft-track-card-content">
<div class="deepdrft-track-info-top">
<MudText Typo="Typo.subtitle1"
Color="Color.Surface"
Class="text-truncate mb-1">
@TrackModel?.TrackName
</MudText>
<MudText Typo="Typo.caption"
Color="Color.Surface"
Class="text-truncate mb-2">
@TrackModel?.Artist
</MudText>
</div>
<div class="deepdrft-track-info-middle">
@if (!string.IsNullOrEmpty(TrackModel?.Album))
{
<MudText Typo="Typo.caption"
Color="Color.Surface"
Class="text-truncate">
@TrackModel.Album
</MudText>
}
@if (!string.IsNullOrEmpty(TrackModel?.Genre))
{
<MudChip T="string"
Size="Size.Small"
Variant="Variant.Filled"
Color="Color.Primary"
Class="deepdrft-genre-chip">
@TrackModel.Genre
</MudChip>
}
</div>
<div class="deepdrft-track-info-bottom">
@if (TrackModel?.ReleaseDate.HasValue == true)
{
<MudText Typo="Typo.caption"
Color="Color.Surface">
@TrackModel.ReleaseDate.Value.Year
</MudText>
}
else
{
<div></div>
}
<MudFab Color="Color.Primary"
Size="Size.Medium"
StartIcon="@PlayPauseIcon"
OnClick="@PlayClick"/>
</div>
</MudCardContent>
</MudCard>
@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Components;
using DeepDrftModels.Entities;
using MudBlazor;
namespace DeepDrftShared.Client.Components;
public partial class TrackCard : ComponentBase
{
[Parameter] public required TrackEntity TrackModel { get; set; }
[Parameter] public EventCallback<TrackEntity> OnPlay { get; set; }
[Parameter] public bool IsPlaying { get; set; } = false;
private string PlayPauseIcon => IsPlaying ? Icons.Material.Filled.MusicNote : Icons.Material.Filled.PlayArrow;
private async Task PlayClick()
{
if (!IsPlaying && OnPlay.HasDelegate)
{
await OnPlay.InvokeAsync(TrackModel);
}
}
}
@@ -0,0 +1,12 @@
<MudContainer MaxWidth="MaxWidth.Large" Class="tracks-gallery-container">
<MudGrid Spacing="6" Justify="Justify.Center">
@foreach (var track in Tracks)
{
<MudItem xs="12" sm="6" md="4" lg="3" xl="3">
<div class="deepdrft-track-gallery-item-center">
<TrackCard TrackModel="@track" IsPlaying="@(track.Id == SelectedTrack?.Id)" OnPlay="@HandlePlayClick"/>
</div>
</MudItem>
}
</MudGrid>
</MudContainer>
@@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Components;
using DeepDrftModels.Entities;
namespace DeepDrftShared.Client.Components;
public partial class TracksGallery : ComponentBase
{
[Parameter] public IEnumerable<TrackEntity> Tracks { get; set; } = [];
[Parameter] public TrackEntity? SelectedTrack { get; set; }
[Parameter] public EventCallback<TrackEntity?> SelectedTrackChanged { get; set; }
private async Task HandlePlayClick(TrackEntity track)
{
if (SelectedTrack == track) return;
SelectedTrack = track;
StateHasChanged();
if (SelectedTrackChanged.HasDelegate)
{
await SelectedTrackChanged.InvokeAsync(track);
}
}
}
@@ -0,0 +1,5 @@
.tracks-gallery-container {
padding: 16px;
height: 100%;
box-sizing: border-box;
}
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<SupportedPlatform Include="browser" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="10.0.7" />
<PackageReference Include="MudBlazor" Version="8.15.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DeepDrftModels\DeepDrftModels.csproj" />
</ItemGroup>
</Project>
+10
View File
@@ -0,0 +1,10 @@
@using System.Net.Http
@using Microsoft.AspNetCore.Components
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using MudBlazor
@using DeepDrftShared.Client.Common
@using DeepDrftShared.Client.Components
@@ -0,0 +1,96 @@
/* DeepDrft design tokens — shared palette layer.
Consumed by both DeepDrftWeb (public site, light + dark) and the CMS host
(light only). Page-specific styling lives in the consuming host's own
stylesheet (e.g. DeepDrftWeb/wwwroot/styles/deepdrft-styles.css). */
/* Light theme - wireframe palette (navy / green / warm off-white) */
:root {
/* Wireframe tokens - source of truth */
--deepdrft-navy: #0D1B2A;
--deepdrft-navy-mid: #162437;
--deepdrft-green: #1A3C34;
--deepdrft-green-light: #2A5C4F;
--deepdrft-green-accent: #3D7A68;
--deepdrft-muted: #8A9BB0;
--deepdrft-white: #FAFAF8;
--deepdrft-border: rgba(13, 27, 42, 0.10);
--deepdrft-border-green: rgba(26, 60, 52, 0.20);
/* Wireframe font stack */
--deepdrft-font-display: "Cormorant Garamond", Georgia, serif;
--deepdrft-font-mono: "Geist Mono", monospace;
--deepdrft-font-body: "DM Sans", sans-serif;
/* Legacy palette aliases - mapped onto wireframe tokens.
Existing utility classes (.deepdrft-card-*, .deepdrft-chip-*, .deepdrft-text-*)
still reference these names; they will be retired in Phase 0.3/0.4 when
Home.razor is rewritten. Until then, alias to the closest wireframe colour. */
--deepdrft-primary: var(--deepdrft-navy);
--deepdrft-secondary: var(--deepdrft-green);
--deepdrft-tertiary: var(--deepdrft-green-accent);
--deepdrft-quaternary: var(--deepdrft-muted);
--deepdrft-quinary: var(--deepdrft-green-light);
--deepdrft-senary: var(--deepdrft-navy-mid);
/* Surfaces */
--deepdrft-surface: var(--deepdrft-white);
--deepdrft-background: var(--deepdrft-white);
--deepdrft-surface-alpha: rgba(250, 250, 248, 0.88);
/* Theme-aware aliases */
--theme-primary: var(--deepdrft-primary);
--theme-secondary: var(--deepdrft-secondary);
--theme-tertiary: var(--deepdrft-tertiary);
--theme-quaternary: var(--deepdrft-quaternary);
--theme-quinary: var(--deepdrft-quinary);
--theme-senary: var(--deepdrft-senary);
--theme-surface: var(--deepdrft-surface);
--theme-surface-soft: var(--deepdrft-muted);
/* Gradient colors */
--gradient-base: var(--deepdrft-white);
--gradient-accent: var(--deepdrft-navy);
--gradient-warm: var(--deepdrft-green);
--gradient-light: var(--deepdrft-green-accent);
/* Legacy font aliases retired in Phase 0.1 — all consumers now use --deepdrft-font-*.
Palette aliases (--deepdrft-primary, --theme-*, etc.) remain; they still have
consumers and are scheduled for retirement in Phase 0.3/0.4. */
}
/* Dark theme - wireframe palette (navy ground / green-accent / off-white).
Mirrors the light palette's vocabulary on a dark ground. Same alias structure
as :root so utility classes (.deepdrft-chip-*, .deepdrft-border-*, .deepdrft-text-*)
resolve coherently across themes. */
.deepdrft-theme-dark {
/* Legacy palette aliases - mapped onto wireframe tokens.
On dark, green-accent becomes the primary interactive colour and
off-white becomes the secondary emphasis. */
--deepdrft-primary: var(--deepdrft-green-accent);
--deepdrft-secondary: var(--deepdrft-white);
--deepdrft-tertiary: var(--deepdrft-green);
--deepdrft-quaternary: var(--deepdrft-muted);
--deepdrft-quinary: var(--deepdrft-green-light);
--deepdrft-senary: var(--deepdrft-navy-mid);
/* Surfaces - navy ground, navy-mid elevated */
--deepdrft-surface: var(--deepdrft-navy-mid);
--deepdrft-background: var(--deepdrft-navy);
--deepdrft-surface-alpha: rgba(13, 27, 42, 0.92);
/* Theme-aware aliases */
--theme-primary: var(--deepdrft-primary);
--theme-secondary: var(--deepdrft-secondary);
--theme-tertiary: var(--deepdrft-tertiary);
--theme-quaternary: var(--deepdrft-quaternary);
--theme-quinary: var(--deepdrft-quinary);
--theme-senary: var(--deepdrft-senary);
--theme-surface: var(--deepdrft-white);
--theme-surface-soft: var(--deepdrft-muted);
/* Gradient colors */
--gradient-base: var(--deepdrft-navy);
--gradient-accent: var(--deepdrft-green-accent);
--gradient-warm: var(--deepdrft-green);
--gradient-light: var(--deepdrft-green-light);
}