mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:41:09 +02:00
feat(workbench): paper-grain polish — blend-mode, border, stone palette
Some checks are pending
CD Mac Mini / Detect Changes (push) Waiting to run
CD Mac Mini / Deploy (push) Blocked by required conditions
CI / Detect Changes (push) Waiting to run
CI / Validate (push) Waiting to run
CI / Auth flow integration test (push) Waiting to run
CI / Build mana-auth (push) Blocked by required conditions
CI / Build mana-search (push) Blocked by required conditions
CI / Build mana-sync (push) Blocked by required conditions
CI / Build mana-notify (push) Blocked by required conditions
CI / Build mana-api-gateway (push) Blocked by required conditions
CI / Build mana-crawler (push) Blocked by required conditions
CI / Build mana-media (push) Blocked by required conditions
CI / Build mana-credits (push) Blocked by required conditions
CI / Build mana-web (push) Blocked by required conditions
CI / Build chat-backend (push) Blocked by required conditions
CI / Build chat-web (push) Blocked by required conditions
CI / Build todo-backend (push) Blocked by required conditions
CI / Build todo-web (push) Blocked by required conditions
CI / Build calendar-backend (push) Blocked by required conditions
CI / Build calendar-web (push) Blocked by required conditions
CI / Build clock-web (push) Blocked by required conditions
CI / Build contacts-backend (push) Blocked by required conditions
CI / Build contacts-web (push) Blocked by required conditions
CI / Build presi-web (push) Blocked by required conditions
CI / Build storage-backend (push) Blocked by required conditions
CI / Build storage-web (push) Blocked by required conditions
CI / Build telegram-stats-bot (push) Blocked by required conditions
CI / Build nutriphi-backend (push) Blocked by required conditions
CI / Build nutriphi-web (push) Blocked by required conditions
CI / Build skilltree-web (push) Blocked by required conditions
Docker Validate / Validate Dockerfiles (push) Waiting to run
Docker Validate / Build calendar-web (push) Blocked by required conditions
Docker Validate / Build todo-backend (push) Blocked by required conditions
Docker Validate / Build todo-web (push) Blocked by required conditions
Docker Validate / Build zitare-web (push) Blocked by required conditions
Docker Validate / Build mana-auth (push) Blocked by required conditions
Docker Validate / Build mana-sync (push) Blocked by required conditions
Docker Validate / Build mana-media (push) Blocked by required conditions
Mirror to Forgejo / Push to Forgejo (push) Waiting to run
Some checks are pending
CD Mac Mini / Detect Changes (push) Waiting to run
CD Mac Mini / Deploy (push) Blocked by required conditions
CI / Detect Changes (push) Waiting to run
CI / Validate (push) Waiting to run
CI / Auth flow integration test (push) Waiting to run
CI / Build mana-auth (push) Blocked by required conditions
CI / Build mana-search (push) Blocked by required conditions
CI / Build mana-sync (push) Blocked by required conditions
CI / Build mana-notify (push) Blocked by required conditions
CI / Build mana-api-gateway (push) Blocked by required conditions
CI / Build mana-crawler (push) Blocked by required conditions
CI / Build mana-media (push) Blocked by required conditions
CI / Build mana-credits (push) Blocked by required conditions
CI / Build mana-web (push) Blocked by required conditions
CI / Build chat-backend (push) Blocked by required conditions
CI / Build chat-web (push) Blocked by required conditions
CI / Build todo-backend (push) Blocked by required conditions
CI / Build todo-web (push) Blocked by required conditions
CI / Build calendar-backend (push) Blocked by required conditions
CI / Build calendar-web (push) Blocked by required conditions
CI / Build clock-web (push) Blocked by required conditions
CI / Build contacts-backend (push) Blocked by required conditions
CI / Build contacts-web (push) Blocked by required conditions
CI / Build presi-web (push) Blocked by required conditions
CI / Build storage-backend (push) Blocked by required conditions
CI / Build storage-web (push) Blocked by required conditions
CI / Build telegram-stats-bot (push) Blocked by required conditions
CI / Build nutriphi-backend (push) Blocked by required conditions
CI / Build nutriphi-web (push) Blocked by required conditions
CI / Build skilltree-web (push) Blocked by required conditions
Docker Validate / Validate Dockerfiles (push) Waiting to run
Docker Validate / Build calendar-web (push) Blocked by required conditions
Docker Validate / Build todo-backend (push) Blocked by required conditions
Docker Validate / Build todo-web (push) Blocked by required conditions
Docker Validate / Build zitare-web (push) Blocked by required conditions
Docker Validate / Build mana-auth (push) Blocked by required conditions
Docker Validate / Build mana-sync (push) Blocked by required conditions
Docker Validate / Build mana-media (push) Blocked by required conditions
Mirror to Forgejo / Push to Forgejo (push) Waiting to run
Switch PageShell's per-theme paper overlay from a ::before + mix-blend-mode + opacity stack to direct background-blend-mode on the element itself. The old approach had invisibility issues in dark mode and stacking-context quirks that made the grain disappear entirely. background-blend-mode against background-color is the simpler, more reliable primitive. utils.ts auto-switches multiply → overlay in dark mode (dark × dark is essentially invisible) while leaving other blend modes as-is. The opacityLight/opacityDark knobs are gone from the paper config since background-blend-mode has no opacity slot — tune via blendMode choice instead. Visual tuning pass: - Card border bumped from 1px box-shadow ring to a real 2px border with background-clip: border-box so the paper texture reads continuously across the edge. Alpha 0.12 light / 0.28 dark (black). - Drop shadow deepened (0 8px 24px + 0 3px 8px) for more card lift. - Stone theme cooled toward real slate-blue: hue 200 → 212, saturation bumped ~10pts across the palette. Stone was reading as warm-neutral grey, now it's a proper cold blue. - Texture remap: Lume → paper-004 (strongest grain, 480px tile for coarser fiber), Stone → cardboard-002 (linen), Lavender → paper-001 (freed up after Stone claimed cardboard-002). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
47aebe3c3b
commit
68c2442419
4 changed files with 88 additions and 97 deletions
|
|
@ -235,58 +235,53 @@
|
|||
|
||||
<style>
|
||||
/* P5: theme-token migration. The workbench paper card now follows the
|
||||
active theme variant. */
|
||||
active theme variant, incl. a per-theme paper-grain texture applied
|
||||
via background-blend-mode (more robust than a ::before overlay +
|
||||
mix-blend-mode + opacity combo, which had invisibility issues in
|
||||
dark mode and stacking-context quirks). CSS vars come from
|
||||
applyThemeToDocument() in @mana/shared-theme — swap one line in
|
||||
THEME_DEFINITIONS to change the texture for a whole theme. */
|
||||
.page-shell {
|
||||
flex: 0 0 auto;
|
||||
min-height: 60vh;
|
||||
max-width: calc(100vw - 2rem);
|
||||
background: hsl(var(--color-card));
|
||||
background-color: hsl(var(--color-card));
|
||||
background-image: var(--paper-texture, none);
|
||||
background-size: var(--paper-size, 240px 240px);
|
||||
background-repeat: repeat;
|
||||
background-blend-mode: var(--paper-blend-mode, multiply);
|
||||
/* Soft black border — 12% in light mode, bumped to 28% in dark
|
||||
mode (see :global(.dark) override below) where a low-alpha
|
||||
black would otherwise vanish into the background. */
|
||||
border: 2px solid hsl(0 0% 0% / 0.12);
|
||||
border-radius: 1.25rem;
|
||||
box-shadow:
|
||||
0 2px 8px hsl(0 0% 0% / 0.08),
|
||||
0 0 0 1px hsl(var(--color-border));
|
||||
0 8px 24px hsl(0 0% 0% / 0.12),
|
||||
0 3px 8px hsl(0 0% 0% / 0.08);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
animation: fadeIn 0.25s ease-out;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
/* Establish a blend-mode stacking context so the grain overlay
|
||||
only blends within this card, not through to the workbench
|
||||
background behind it. */
|
||||
isolation: isolate;
|
||||
}
|
||||
/* Dark-mode border needs more alpha to stay visible against the
|
||||
dark card background. */
|
||||
:global(.dark) .page-shell {
|
||||
border-color: hsl(0 0% 0% / 0.28);
|
||||
}
|
||||
|
||||
/* Per-theme paper-grain overlay. CSS variables come from
|
||||
applyThemeToDocument() in @mana/shared-theme — swap one line in
|
||||
THEME_DEFINITIONS to change the texture for a whole theme. */
|
||||
.page-shell::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
background-image: var(--paper-texture, none);
|
||||
background-size: var(--paper-size, 240px 240px);
|
||||
background-repeat: repeat;
|
||||
mix-blend-mode: var(--paper-blend-mode, multiply);
|
||||
opacity: var(--paper-opacity, 0);
|
||||
transition: opacity 0.25s ease;
|
||||
}
|
||||
/* Make sure the header, body and resize handle sit above the grain */
|
||||
.page-shell > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
/* A11y: let users who asked for reduced transparency/contrast opt out */
|
||||
/* A11y: users who asked for reduced transparency/contrast drop the
|
||||
texture entirely — solid card only. */
|
||||
@media (prefers-contrast: more) {
|
||||
.page-shell::before {
|
||||
opacity: 0;
|
||||
.page-shell {
|
||||
background-image: none;
|
||||
}
|
||||
}
|
||||
.page-shell.resizing {
|
||||
border-color: hsl(var(--color-primary) / 0.55);
|
||||
box-shadow:
|
||||
0 2px 12px hsl(var(--color-primary) / 0.15),
|
||||
0 0 0 2px hsl(var(--color-primary) / 0.3);
|
||||
0 10px 28px hsl(var(--color-primary) / 0.22),
|
||||
0 4px 10px hsl(var(--color-primary) / 0.15);
|
||||
}
|
||||
.page-shell.maximized {
|
||||
position: fixed;
|
||||
|
|
|
|||
|
|
@ -114,46 +114,49 @@ const natureDark: ThemeColors = {
|
|||
ring: '122 39% 49%',
|
||||
};
|
||||
|
||||
// Stone: cooled toward a proper blue-slate. Hue shifted 200 → 212
|
||||
// (more blue, less cyan) and saturation bumped ~10pts across the
|
||||
// palette so the chill actually reads instead of looking like warm grey.
|
||||
const stoneLight: ThemeColors = {
|
||||
primary: '200 18% 46%', // #607D8B - Blue gray
|
||||
primary: '212 32% 44%', // #4A6B8C - Deeper slate-blue
|
||||
primaryForeground: '0 0% 100%',
|
||||
secondary: '200 15% 62%', // #90A4AE - Light slate
|
||||
secondary: '212 26% 60%', // #7E9CB8 - Cool slate
|
||||
secondaryForeground: '0 0% 0%',
|
||||
background: '210 17% 97%', // #F5F7F9 - Very light blue gray
|
||||
foreground: '200 19% 18%', // #263238 - Dark slate
|
||||
surface: '0 0% 100%',
|
||||
surfaceHover: '200 10% 94%', // #ECEFF1
|
||||
surfaceElevated: '0 0% 100%',
|
||||
muted: '200 10% 94%', // #ECEFF1
|
||||
mutedForeground: '200 10% 45%',
|
||||
border: '200 10% 88%', // #CFD8DC
|
||||
borderStrong: '200 12% 75%', // #B0BEC5
|
||||
error: '4 90% 63%', // #EF5350
|
||||
background: '214 30% 96%', // #EEF2F7 - Icy blue-white
|
||||
foreground: '215 32% 16%', // #1B2835 - Deep slate
|
||||
surface: '210 40% 99%',
|
||||
surfaceHover: '212 24% 92%', // #E0E7EF
|
||||
surfaceElevated: '210 40% 99%',
|
||||
muted: '212 22% 92%', // #DFE6EE
|
||||
mutedForeground: '212 18% 42%',
|
||||
border: '212 22% 84%', // #C4D0DE
|
||||
borderStrong: '212 24% 70%', // #9FB2C6
|
||||
error: '4 90% 63%',
|
||||
success: '145 63% 42%',
|
||||
warning: '36 100% 50%',
|
||||
input: '0 0% 100%',
|
||||
ring: '200 18% 46%',
|
||||
input: '210 40% 99%',
|
||||
ring: '212 32% 44%',
|
||||
};
|
||||
|
||||
const stoneDark: ThemeColors = {
|
||||
primary: '200 15% 52%', // #78909C - Lighter in dark mode
|
||||
primary: '212 28% 56%', // #6F8DAC - Brighter slate-blue for dark
|
||||
primaryForeground: '0 0% 0%',
|
||||
secondary: '200 12% 35%',
|
||||
secondary: '212 20% 38%',
|
||||
secondaryForeground: '0 0% 100%',
|
||||
background: '210 15% 8%', // Very dark with blue-gray tint
|
||||
foreground: '0 0% 100%',
|
||||
surface: '200 12% 12%',
|
||||
surfaceHover: '200 12% 16%',
|
||||
surfaceElevated: '200 12% 14%',
|
||||
muted: '200 10% 20%',
|
||||
mutedForeground: '200 10% 60%',
|
||||
border: '200 10% 25%',
|
||||
borderStrong: '200 10% 35%',
|
||||
background: '215 30% 8%', // Very dark with clear blue cast
|
||||
foreground: '210 20% 96%',
|
||||
surface: '214 24% 12%',
|
||||
surfaceHover: '214 22% 17%',
|
||||
surfaceElevated: '214 22% 15%',
|
||||
muted: '212 18% 22%',
|
||||
mutedForeground: '212 18% 62%',
|
||||
border: '212 18% 28%',
|
||||
borderStrong: '212 18% 40%',
|
||||
error: '4 90% 58%',
|
||||
success: '145 63% 49%',
|
||||
warning: '48 100% 50%',
|
||||
input: '200 10% 14%',
|
||||
ring: '200 15% 52%',
|
||||
input: '214 22% 15%',
|
||||
ring: '212 28% 56%',
|
||||
};
|
||||
|
||||
const oceanLight: ThemeColors = {
|
||||
|
|
@ -388,11 +391,14 @@ export const THEME_DEFINITIONS: Record<ThemeVariant, ThemeVariantDefinition> = {
|
|||
hue: 47,
|
||||
light: lumeLight,
|
||||
dark: lumeDark,
|
||||
// Lume wants a stronger, more contrasty grain — paper-004 has
|
||||
// the heaviest fiber detail in the set, multiply makes it bite.
|
||||
// Larger tile so the grain pattern reads as big paper fibers
|
||||
// instead of fine noise.
|
||||
paper: {
|
||||
url: '/textures/paper/paper-001.jpg',
|
||||
url: '/textures/paper/paper-004.jpg',
|
||||
blendMode: 'multiply',
|
||||
opacityLight: 0.4,
|
||||
opacityDark: 0.18,
|
||||
size: '480px 480px',
|
||||
},
|
||||
},
|
||||
nature: {
|
||||
|
|
@ -406,8 +412,6 @@ export const THEME_DEFINITIONS: Record<ThemeVariant, ThemeVariantDefinition> = {
|
|||
paper: {
|
||||
url: '/textures/paper/cardboard-001.jpg',
|
||||
blendMode: 'multiply',
|
||||
opacityLight: 0.32,
|
||||
opacityDark: 0.14,
|
||||
},
|
||||
},
|
||||
stone: {
|
||||
|
|
@ -415,14 +419,14 @@ export const THEME_DEFINITIONS: Record<ThemeVariant, ThemeVariantDefinition> = {
|
|||
label: 'Stone',
|
||||
emoji: '🪨',
|
||||
icon: 'hexagon',
|
||||
hue: 200,
|
||||
hue: 212,
|
||||
light: stoneLight,
|
||||
dark: stoneDark,
|
||||
// Stone gets the linen-like cardboard texture — more tactile
|
||||
// fiber pattern than the old paper-005 (which felt too smooth).
|
||||
paper: {
|
||||
url: '/textures/paper/paper-005.jpg',
|
||||
url: '/textures/paper/cardboard-002.jpg',
|
||||
blendMode: 'multiply',
|
||||
opacityLight: 0.35,
|
||||
opacityDark: 0.15,
|
||||
},
|
||||
},
|
||||
ocean: {
|
||||
|
|
@ -436,8 +440,6 @@ export const THEME_DEFINITIONS: Record<ThemeVariant, ThemeVariantDefinition> = {
|
|||
paper: {
|
||||
url: '/textures/paper/paper-003.jpg',
|
||||
blendMode: 'multiply',
|
||||
opacityLight: 0.3,
|
||||
opacityDark: 0.12,
|
||||
},
|
||||
},
|
||||
// Extended themes (not in PillNav by default, can be pinned)
|
||||
|
|
@ -452,8 +454,6 @@ export const THEME_DEFINITIONS: Record<ThemeVariant, ThemeVariantDefinition> = {
|
|||
paper: {
|
||||
url: '/textures/paper/paper-006.jpg',
|
||||
blendMode: 'multiply',
|
||||
opacityLight: 0.38,
|
||||
opacityDark: 0.16,
|
||||
},
|
||||
},
|
||||
midnight: {
|
||||
|
|
@ -467,8 +467,6 @@ export const THEME_DEFINITIONS: Record<ThemeVariant, ThemeVariantDefinition> = {
|
|||
paper: {
|
||||
url: '/textures/paper/paper-004.jpg',
|
||||
blendMode: 'overlay',
|
||||
opacityLight: 0.35,
|
||||
opacityDark: 0.22,
|
||||
},
|
||||
},
|
||||
rose: {
|
||||
|
|
@ -482,8 +480,6 @@ export const THEME_DEFINITIONS: Record<ThemeVariant, ThemeVariantDefinition> = {
|
|||
paper: {
|
||||
url: '/textures/paper/paper-002.jpg',
|
||||
blendMode: 'multiply',
|
||||
opacityLight: 0.32,
|
||||
opacityDark: 0.14,
|
||||
},
|
||||
},
|
||||
lavender: {
|
||||
|
|
@ -494,11 +490,11 @@ export const THEME_DEFINITIONS: Record<ThemeVariant, ThemeVariantDefinition> = {
|
|||
hue: 270,
|
||||
light: lavenderLight,
|
||||
dark: lavenderDark,
|
||||
// Freed up after Stone claimed cardboard-002 — paper-001 gives
|
||||
// lavender a softer, smoother fiber than the rough cardboard.
|
||||
paper: {
|
||||
url: '/textures/paper/cardboard-002.jpg',
|
||||
url: '/textures/paper/paper-001.jpg',
|
||||
blendMode: 'soft-light',
|
||||
opacityLight: 0.4,
|
||||
opacityDark: 0.18,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -105,16 +105,21 @@ export interface ThemeVariantDefinition {
|
|||
light: ThemeColors;
|
||||
dark: ThemeColors;
|
||||
/**
|
||||
* Optional "paper grain" overlay for workbench page surfaces.
|
||||
* Each theme can ship its own tileable texture to give pages a
|
||||
* Optional "paper grain" texture for workbench page surfaces.
|
||||
* Each theme can ship its own tileable texture applied via
|
||||
* background-blend-mode on the page-shell card so pages gain a
|
||||
* distinct tactile character. The consuming app is responsible for
|
||||
* serving the asset at the given URL (typically under `/textures/`).
|
||||
* When undefined, no paper overlay is applied for this theme.
|
||||
* When undefined, no paper texture is applied for this theme.
|
||||
*
|
||||
* Note: in dark mode, `multiply` auto-falls-back to `overlay` since
|
||||
* dark × dark is practically invisible. Other blend modes are kept
|
||||
* as-is for both modes.
|
||||
*/
|
||||
paper?: {
|
||||
/** URL / absolute path to a seamless tileable texture */
|
||||
url: string;
|
||||
/** CSS blend-mode for the overlay vs. the underlying card bg */
|
||||
/** CSS background-blend-mode against the card color */
|
||||
blendMode?:
|
||||
| 'multiply'
|
||||
| 'overlay'
|
||||
|
|
@ -124,10 +129,6 @@ export interface ThemeVariantDefinition {
|
|||
| 'darken'
|
||||
| 'lighten'
|
||||
| 'color-burn';
|
||||
/** Opacity of the paper layer in light mode (0..1, default 0.35) */
|
||||
opacityLight?: number;
|
||||
/** Opacity of the paper layer in dark mode (0..1, default 0.15) */
|
||||
opacityDark?: number;
|
||||
/** CSS background-size (default "240px 240px") */
|
||||
size?: string;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -113,21 +113,20 @@ export function applyThemeToDocument(
|
|||
});
|
||||
|
||||
// Set per-theme paper-grain CSS variables (consumed by PageShell).
|
||||
// Unset the vars for themes without a paper config so they don't
|
||||
// leak across theme switches.
|
||||
// Dark mode auto-switches multiply → overlay because multiply on a
|
||||
// dark backdrop is practically invisible (dark × dark ≈ dark). Other
|
||||
// blend modes (soft-light/screen/…) are respected as-is.
|
||||
const paper = THEME_DEFINITIONS[variant]?.paper;
|
||||
if (paper) {
|
||||
const configuredBlend = paper.blendMode ?? 'multiply';
|
||||
const effectiveBlend =
|
||||
effectiveMode === 'dark' && configuredBlend === 'multiply' ? 'overlay' : configuredBlend;
|
||||
root.style.setProperty('--paper-texture', `url("${paper.url}")`);
|
||||
root.style.setProperty('--paper-blend-mode', paper.blendMode ?? 'multiply');
|
||||
root.style.setProperty(
|
||||
'--paper-opacity',
|
||||
String(effectiveMode === 'dark' ? (paper.opacityDark ?? 0.15) : (paper.opacityLight ?? 0.35))
|
||||
);
|
||||
root.style.setProperty('--paper-blend-mode', effectiveBlend);
|
||||
root.style.setProperty('--paper-size', paper.size ?? '240px 240px');
|
||||
} else {
|
||||
root.style.removeProperty('--paper-texture');
|
||||
root.style.removeProperty('--paper-blend-mode');
|
||||
root.style.removeProperty('--paper-opacity');
|
||||
root.style.removeProperty('--paper-size');
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue