From a270d965fe666d479804c3a302f3b520fad00a61 Mon Sep 17 00:00:00 2001 From: JCEEE <0xjceee@proton.me> Date: Thu, 10 Sep 2026 13:23:55 +0100 Subject: [PATCH] add theme patterns --- frontend/src/hooks.server.ts | 5 +- .../src/lib/components/PatternPicker.svelte | 184 ++++ frontend/src/lib/components/index.ts | 1 + frontend/src/lib/server/migrate.ts | 12 + frontend/src/lib/server/services/fam.ts | 25 +- frontend/src/lib/server/types.ts | 5 +- frontend/src/lib/stores/theme.svelte.ts | 14 + frontend/src/lib/theme-patterns.css | 991 ++++++++++++++++++ frontend/src/lib/theme.ts | 61 ++ frontend/src/routes/[fam]/+layout.server.ts | 3 + frontend/src/routes/[fam]/+layout.svelte | 22 +- .../[username]/preferences/+page.server.ts | 35 +- .../[fam]/[username]/preferences/+page.svelte | 98 +- 13 files changed, 1429 insertions(+), 27 deletions(-) create mode 100644 frontend/src/lib/components/PatternPicker.svelte create mode 100644 frontend/src/lib/stores/theme.svelte.ts create mode 100644 frontend/src/lib/theme-patterns.css create mode 100644 frontend/src/lib/theme.ts diff --git a/frontend/src/hooks.server.ts b/frontend/src/hooks.server.ts index 65c3426..715172d 100644 --- a/frontend/src/hooks.server.ts +++ b/frontend/src/hooks.server.ts @@ -55,7 +55,10 @@ export const handle: Handle = async ({ event, resolve }) => { username: handleOf(record.username || ''), role: record.role || 'parent', famId: record.famId, - color: record.color || '' + color: record.color || '', + pattern: record.pattern || '', + themeSize: record.themeSize || '', + themeOpacity: record.themeOpacity || '' } satisfies SessionUser; event.locals.pbToken = freshToken; diff --git a/frontend/src/lib/components/PatternPicker.svelte b/frontend/src/lib/components/PatternPicker.svelte new file mode 100644 index 0000000..9040d0c --- /dev/null +++ b/frontend/src/lib/components/PatternPicker.svelte @@ -0,0 +1,184 @@ + + +
+ + {#each Array(PATTERN_COUNT) as _, i} + {@const id = patternId(i + 1)} + + {/each} +
+ +{#if value !== ''} + {#if onsize} +
+
+ + {sizeVal} +
+ onsize?.(sizeVal)} + /> + scales with screen width +
+ {/if} + {#if onopacity} +
+
+ + {Math.round(opacityVal * 100)}% +
+ onopacity?.(opacityVal)} + /> + max = current look +
+ {/if} +{/if} + + diff --git a/frontend/src/lib/components/index.ts b/frontend/src/lib/components/index.ts index 8887ba6..4edd5f6 100644 --- a/frontend/src/lib/components/index.ts +++ b/frontend/src/lib/components/index.ts @@ -13,3 +13,4 @@ export { default as AuthShell } from './AuthShell.svelte'; export { default as NoticeDialog } from './NoticeDialog.svelte'; export { default as PricingPlans } from './PricingPlans.svelte'; export { default as CheckboxGrid } from './CheckboxGrid.svelte'; +export { default as PatternPicker } from './PatternPicker.svelte'; diff --git a/frontend/src/lib/server/migrate.ts b/frontend/src/lib/server/migrate.ts index bec69f1..28bbfee 100644 --- a/frontend/src/lib/server/migrate.ts +++ b/frontend/src/lib/server/migrate.ts @@ -146,6 +146,18 @@ async function ensureUsers(ids: Record): Promise { usersCol.fields.push({ name: 'color', type: 'text', required: false }); changed = true; } + if (!has('pattern')) { + usersCol.fields.push({ name: 'pattern', type: 'text', required: false }); + changed = true; + } + if (!has('themeSize')) { + usersCol.fields.push({ name: 'themeSize', type: 'text', required: false }); + changed = true; + } + if (!has('themeOpacity')) { + usersCol.fields.push({ name: 'themeOpacity', type: 'text', required: false }); + changed = true; + } if (!has('passwordResetToken')) { usersCol.fields.push({ name: 'passwordResetToken', type: 'text', required: false }); changed = true; diff --git a/frontend/src/lib/server/services/fam.ts b/frontend/src/lib/server/services/fam.ts index 809e61b..12e1a18 100644 --- a/frontend/src/lib/server/services/fam.ts +++ b/frontend/src/lib/server/services/fam.ts @@ -69,7 +69,15 @@ export async function patchFam(pb: any, famId: string, body: Record = {}; if (data.name) patch.name = data.name; if (data.color) patch.color = data.color; + if (data.pattern !== undefined) patch.pattern = data.pattern; + if (data.themeSize !== undefined) patch.themeSize = data.themeSize; + if (data.themeOpacity !== undefined) patch.themeOpacity = data.themeOpacity; if (data.email !== undefined) patch.email = data.email; const rec = await pb.collection('users').update(userId, patch); - return { id: rec.id, name: rec.name || '', color: rec.color || '#6366f1', email: rec.email || '' }; + return { + id: rec.id, + name: rec.name || '', + color: rec.color || '#6366f1', + pattern: rec.pattern || '', + themeSize: rec.themeSize || '', + themeOpacity: rec.themeOpacity || '', + email: rec.email || '' + }; } export async function weeklySummary(pb: any, famId: string) { @@ -309,4 +328,4 @@ export async function completeWeek(pb: any, famId: string) { historyRecords, memberCount: members.length }; -} \ No newline at end of file +} diff --git a/frontend/src/lib/server/types.ts b/frontend/src/lib/server/types.ts index 6e97c34..e731727 100644 --- a/frontend/src/lib/server/types.ts +++ b/frontend/src/lib/server/types.ts @@ -5,4 +5,7 @@ export interface SessionUser { role: 'parent' | 'child'; famId: string; color?: string; -} \ No newline at end of file + pattern?: string; + themeSize?: string; + themeOpacity?: string; +} diff --git a/frontend/src/lib/stores/theme.svelte.ts b/frontend/src/lib/stores/theme.svelte.ts new file mode 100644 index 0000000..c7587df --- /dev/null +++ b/frontend/src/lib/stores/theme.svelte.ts @@ -0,0 +1,14 @@ +// Live theme draft — the PatternPicker writes here the moment a tile (or a +// colour swatch) is clicked, so the fam layout can preview the background +// instantly. `null` means "no draft yet — use the saved value from session". +export const themeDraft = $state<{ + pattern: string | null; + color: string | null; + size: number | null; + opacity: number | null; +}>({ + pattern: null, + color: null, + size: null, + opacity: null +}); diff --git a/frontend/src/lib/theme-patterns.css b/frontend/src/lib/theme-patterns.css new file mode 100644 index 0000000..633ccc8 --- /dev/null +++ b/frontend/src/lib/theme-patterns.css @@ -0,0 +1,991 @@ +/* Generated from css-patterns.css (see scripts/gen-theme-patterns.mjs). + 57 unique patterns (3 duplicates removed). + Colors are overridden per-user via --c1..--c4 set inline on the element + (hsl shades of the chosen swatch). */ + +html .app-shell.pattern-p1, +.pattern-p1 { + --s: 84px; /* control the size*/ + --c1: #f2f2f2; + --c2: #cdcbcc; + --c3: #999999; + + --_g: 0 120deg,#0000 0; + background: + conic-gradient( at calc(250%/3) calc(100%/3),var(--c3) var(--_g)), + conic-gradient(from -120deg at calc( 50%/3) calc(100%/3),var(--c2) var(--_g)), + conic-gradient(from 120deg at calc(100%/3) calc(250%/3),var(--c1) var(--_g)), + conic-gradient(from 120deg at calc(200%/3) calc(250%/3),var(--c1) var(--_g)), + conic-gradient(from -180deg at calc(100%/3) 50%,var(--c2) 60deg,var(--c1) var(--_g)), + conic-gradient(from 60deg at calc(200%/3) 50%,var(--c1) 60deg,var(--c3) var(--_g)), + conic-gradient(from -60deg at 50% calc(100%/3),var(--c1) 120deg,var(--c2) 0 240deg,var(--c3) 0); + background-size: calc(var(--s)*sqrt(3)) var(--s); +} + +html .app-shell.pattern-p2, +.pattern-p2 { + --s: 200px; /* control the size*/ + --c1: #1d1d1d; + --c2: #4e4f51; + --c3: #3c3c3c; + + background: + repeating-conic-gradient(from 30deg,#0000 0 120deg,var(--c3) 0 50%) + calc(var(--s)/2) calc(var(--s)*tan(30deg)/2), + repeating-conic-gradient(from 30deg,var(--c1) 0 60deg,var(--c2) 0 120deg,var(--c3) 0 50%); + background-size: var(--s) calc(var(--s)*tan(30deg)); +} + +html .app-shell.pattern-p3, +.pattern-p3 { + --s: 64px; /* control the size*/ + --c1: #c02942; + --c2: #53777a; + --c3: #ecd078; + --c4: #d95b43; + + background: + radial-gradient(var(--c1) 24%,#0000 25%), + radial-gradient(var(--c2) 30%,#0000 32%) calc(var(--s)/2) calc(var(--s)/2), + repeating-conic-gradient(from 30deg,var(--c3) 0 30deg,var(--c4) 0 25%); + background-size: var(--s) var(--s); +} + +html .app-shell.pattern-p4, +.pattern-p4 { + --s: 100px; /* control the size*/ + --c1: #fff0e5; + --c2: #025d8c; + --c3: #e1642a; + + --_g: 50%,#0000 37%,var(--c1) 39% 70%,#0000 72%; + --_t: 50%,var(--c2) 40deg,var(--c3) 0 140deg,var(--c2) 0 180deg,#0000 0; + --_s: 47% 50% at; + background: + radial-gradient(var(--_s) -10% var(--_g)) 0 calc(var(--s)/2), + radial-gradient(var(--_s) -10% var(--_g)) calc(var(--s)/2) 0, + radial-gradient(var(--_s) 110% var(--_g)), + radial-gradient(var(--_s) 110% var(--_g)) calc(var(--s)/2) calc(var(--s)/2), + conic-gradient(from 0deg at 55% var(--_t)) calc(var(--s)/4) 0, + conic-gradient(from 180deg at 45% var(--_t)) calc(var(--s)/4) 0, + var(--c2); + background-size: var(--s) var(--s); +} + +html .app-shell.pattern-p5, +.pattern-p5 { + --s: 150px; /* control the size*/ + --c1: #f7d2a1; + --c2: #05057e; + + --_g: + var(--c1) 0% 5% ,var(--c2) 6% 15%,var(--c1) 16% 25%,var(--c2) 26% 35%,var(--c1) 36% 45%, + var(--c2) 46% 55%,var(--c1) 56% 65%,var(--c2) 66% 75%,var(--c1) 76% 85%,var(--c2) 86% 95%, + #0000 96%; + background: + radial-gradient(50% 50% at 100% 0,var(--_g)), + radial-gradient(50% 50% at 0 100%,var(--_g)), + radial-gradient(50% 50%,var(--_g)), + radial-gradient(50% 50%,var(--_g)) calc(var(--s)/2) calc(var(--s)/2) + var(--c1); + background-size: var(--s) var(--s); +} + +html .app-shell.pattern-p6, +.pattern-p6 { + --s: 120px; /* control the size*/ + --c1: #fa6900; + --c2: #d95b43; + --c3: #ecd078; + + background: + conic-gradient(from 150deg at 50% 33%,#0000,var(--c1) .5deg 60deg,#0000 60.5deg) + calc(var(--s)/2) calc(var(--s)/sqrt(2)), + conic-gradient(from -30deg at 50% 66%,#0000,var(--c2) .5deg 60deg,var(--c3) 60.5deg); + background-size: var(--s) calc(.5*var(--s)/tan(30deg)); +} + +html .app-shell.pattern-p7, +.pattern-p7 { + --s: 100px; /* control the size*/ + --c1: #4ecdc4; + --c2: #556270; + + background: + linear-gradient(atan(-.5),var(--c1) 33%,var(--c2) 33.5% 66.5%,var(--c1) 67%) + 0/var(--s) var(--s); +} + +html .app-shell.pattern-p8, +.pattern-p8 { + --s: 100px; /* control the size*/ + --c1: #eceddc; + --c2: #29ab87; + + --_g: var(--c1) 90deg,#0000 90.5deg; + background: + conic-gradient(from -45deg,var(--_g)), + conic-gradient(from 135deg,var(--_g)) calc(var(--s)/2) 0, + var(--c2); + background-size: var(--s) var(--s) +} + +html .app-shell.pattern-p9, +.pattern-p9 { + --s: 150px; /* control the size*/ + --c1: #ff847c; + --c2: #e84a5f; + --c3: #fecea8; + --c4: #99b898; + + background: + conic-gradient(from 45deg at 75% 75%, var(--c3) 90deg,var(--c1) 0 180deg,#0000 0), + conic-gradient(from -45deg at 25% 25%, var(--c3) 90deg,#0000 0), + conic-gradient(from -45deg at 50% 100%,#0000 180deg,var(--c3) 0), + conic-gradient(from -45deg,var(--c1) 90deg, var(--c2) 0 225deg,var(--c4) 0); + background-size: var(--s) var(--s); +} + +html .app-shell.pattern-p10, +.pattern-p10 { + --s: 222px; /* control the size*/ + --c1: #7f727b; + --c2: #d6b4c2; + --c3: #baa0ab; + + --_g: var(--c1) 10%,var(--c2) 10.5% 19%,#0000 19.5% 80.5%,var(--c2) 81% 89.5%,var(--c3) 90%; + --_c: from -90deg at 37.5% 50%,#0000 75%; + --_l1: linear-gradient(145deg,var(--_g)); + --_l2: linear-gradient( 35deg,var(--_g)); + background: + var(--_l1), var(--_l1) calc(var(--s)/2) var(--s), + var(--_l2), var(--_l2) calc(var(--s)/2) var(--s), + conic-gradient(var(--_c),var(--c1) 0) calc(var(--s)/8) 0, + conic-gradient(var(--_c),var(--c3) 0) calc(var(--s)/2) 0, + linear-gradient(90deg,var(--c3) 38%,var(--c1) 0 50%,var(--c3) 0 62%,var(--c1) 0); + background-size: var(--s) calc(2*var(--s)/3); +} + +html .app-shell.pattern-p11, +.pattern-p11 { + --s: 20px; /* control the size*/ + --c1: #c02942; + --c2: #53777a; + --c3: #ecd078; + + --g: var(--s); /* gap between lines */ + background: + conic-gradient(at var(--s) calc(100% - var(--s)),#0000 270deg,var(--c1) 0) calc(var(--s) + var(--g)) 0, + linear-gradient(var(--c2) var(--s),#0000 0) 0 var(--g), + conic-gradient(at var(--s) calc(100% - var(--s)),#0000 90deg,var(--c2) 0 180deg, var(--c1) 0), + var(--c3); + background-size: calc(2*(var(--s) + var(--g))) calc(2*(var(--s) + var(--g))); +} + +html .app-shell.pattern-p12, +.pattern-p12 { + --s: 200px; /* control the size*/ + --c1: #ffffff; + --c2: #1095c1; + + --_g: #0000 8%,var(--c1) 0 17%,#0000 0 58%; + background: + linear-gradient(135deg,#0000 20.5%,var(--c1) 0 29.5%,#0000 0) 0 calc(var(--s)/4), + linear-gradient( 45deg,var(--_g)) calc(var(--s)/2) 0, + linear-gradient(135deg,var(--_g),var(--c1) 0 67%,#0000 0), + linear-gradient( 45deg,var(--_g),var(--c1) 0 67%,#0000 0 83%,var(--c1) 0 92%,#0000 0), + var(--c2); + background-size: var(--s) var(--s); +} + +html .app-shell.pattern-p13, +.pattern-p13 { + --s: 64px; /* control the size*/ + --c1: #e0e4cc; + --c2: #69d2e7; + + --_g: 90deg,#0000 0; + background: + conic-gradient(from 135deg,var(--c1) var(--_g)) var(--s) calc(var(--s)/2), + conic-gradient(from 135deg,var(--c2) var(--_g)), + conic-gradient(from 135deg at 50% 0,var(--c1) var(--_g)) var(--c2); + background-size: calc(2*var(--s)) var(--s); +} + +html .app-shell.pattern-p14, +.pattern-p14 { + --s: 64px; /* control the size*/ + --c1: #eb6841; + --c2: #edc951; + + background: + conic-gradient(from -45deg,var(--c1) 90deg,#0000 0 180deg,var(--c2) 0 270deg,#0000 0) + 0 calc(var(--s)/2)/var(--s) var(--s), + conic-gradient(from 135deg at 50% 0,var(--c1) 90deg,var(--c2) 0) + 0 0/calc(2*var(--s)) var(--s); +} + +html .app-shell.pattern-p15, +.pattern-p15 { + --s: 36px; /* control the size*/ + --c1: #b62f31; + --c2: #ecdacb; + --c3: #8e1f08; + + --_g: calc(2*var(--s)*1.732) calc(2*var(--s)) + conic-gradient(from 60deg at 62.5% 50%,var(--c3) 60deg,#0000 0); + background: + calc( 2.598*var(--s)/2) calc(var(--s)/ 2)/var(--_g), + calc(-0.866*var(--s)/2) calc(var(--s)/-2)/var(--_g), + repeating-conic-gradient(var(--c2) 0 90deg,#0000 0 180deg) + 0 0/calc(2*var(--s)*1.732) calc(2*var(--s)), + repeating-conic-gradient(from 60deg,var(--c1) 0 60deg,var(--c2) 0 180deg) + 0 0/calc(var(--s)*1.732) var(--s); +} + +html .app-shell.pattern-p16, +.pattern-p16 { + --s: 125px; /* control the size*/ + --c1: #f8ca00; + --c2: #bd1550; + + --_g: var(--c1) 90deg,var(--c2) 0 135deg,#0000 0; + background: + conic-gradient(from -45deg at calc(100%/3) calc(100%/3) ,var(--c1) 90deg,#0000 0 ), + conic-gradient(from -135deg at calc(100%/3) calc(2*100%/3),var(--_g)), + conic-gradient(from 135deg at calc(2*100%/3) calc(2*100%/3),var(--_g)), + conic-gradient(from 45deg at calc(2*100%/3) calc(100%/3) ,var(--_g),var(--c1) 0 225deg,var(--c2) 0); + background-size: var(--s) var(--s); +} + +html .app-shell.pattern-p17, +.pattern-p17 { + --s: 120px; /* control the size*/ + --c1: #72e21f; + --c2: #044012; + + --_c: var(--c1) 25%,#0000 0; + --_g1: conic-gradient(at 62.5% 12.5%,var(--_c)); + --_g2: conic-gradient(at 87.5% 62.5%,var(--_c)); + --_g3: conic-gradient(at 25% 12.5%,var(--_c)); + background: + var(--_g1) calc( var(--s)/-8) calc(var(--s)/2),var(--_g1) calc(-3*var(--s)/8) calc(var(--s)/4), + var(--_g2) calc(3*var(--s)/8) calc(var(--s)/4),var(--_g2) calc( var(--s)/-8) 0, + var(--_g3) 0 calc(var(--s)/-4),var(--_g3) calc(var(--s)/-4) 0, + conic-gradient(at 87.5% 87.5%,var(--_c)) calc(var(--s)/8) 0 + var(--c2); + background-size: var(--s) var(--s); +} + +html .app-shell.pattern-p18, +.pattern-p18 { + --s: 170px; /* control the size*/ + --c1: #3b8686; + --c2: #cff09e; + + --_c: #0000 75%,var(--c1) 0; + --_g1: conic-gradient(at 20.71% 50%,var(--_c)); + --_g2: conic-gradient(at 50% 20.71%,var(--_c)); + background: + var(--_g1) calc(var(--s)/ 6.828) calc(var(--s)/2),var(--_g1) calc(var(--s)/-2.828) 0, + conic-gradient(from 45deg at 29.29% 29.29%,var(--_c)) calc(var(--s)/-6.828) 0, + var(--_g2) calc(var(--s)/2) calc(var(--s)/-2.828),var(--_g2) 0 calc(var(--s)/ 6.828), + conic-gradient(from -135deg at 29.29% 70.71%,var(--_c)) calc(var(--s)/-6.828) 0, + var(--c2); + background-size: var(--s) var(--s), var(--s) var(--s), calc(var(--s)/2) calc(var(--s)/2); +} + +html .app-shell.pattern-p19, +.pattern-p19 { + --s: 76px; /* control the size*/ + --c1: #f6d86b; + --c2: #f10c49; + + background: + conic-gradient( + var(--c2) atan(2), var(--c1) 0 calc(180deg - atan(2)), + var(--c2) 0 180deg,var(--c1) 0 calc(180deg + atan(2)), + var(--c2) 0 calc(360deg - atan(2)),var(--c1) 0); + background-size: calc(2*var(--s)) var(--s); +} + +html .app-shell.pattern-p20, +.pattern-p20 { + --s: 40px; /* control the size*/ + --c1: #c0d860; + --c2: #604848; + + --_c: #0000 75%,var(--c1) 0; + --_g1: conic-gradient(at 10% 50%,var(--_c)); + --_g2: conic-gradient(at 50% 10%,var(--_c)); + background: + var(--_g1), + var(--_g1) calc(1*var(--s)) calc(3*var(--s)), + var(--_g1) calc(2*var(--s)) calc(1*var(--s)), + var(--_g1) calc(3*var(--s)) calc(4*var(--s)), + var(--_g1) calc(4*var(--s)) calc(2*var(--s)), + var(--_g2) 0 calc(4*var(--s)), + var(--_g2) calc(1*var(--s)) calc(2*var(--s)), + var(--_g2) calc(2*var(--s)) 0, + var(--_g2) calc(3*var(--s)) calc(3*var(--s)), + var(--_g2) calc(4*var(--s)) calc(1*var(--s)), + var(--c2); + background-size: calc(5*var(--s)) calc(5*var(--s)); +} + +html .app-shell.pattern-p21, +.pattern-p21 { + --s: 65px; /* control the size*/ + --c1: #dadee1; + --c2: #4a99b4; + --c3: #9cceb5; + + --_c: 75%,var(--c3) 52.72deg,#0000 0; + --_g1: conic-gradient(from -116.36deg at 25% var(--_c)); + --_g2: conic-gradient(from 63.43deg at 75% var(--_c)); + background: + var(--_g1), var(--_g1) calc(3*var(--s)) calc(var(--s)/2), + var(--_g2), var(--_g2) calc(3*var(--s)) calc(var(--s)/2), + conic-gradient( + var(--c2) 63.43deg ,var(--c1) 0 116.36deg, + var(--c2) 0 180deg ,var(--c1) 0 243.43deg, + var(--c2) 0 296.15deg,var(--c1) 0); + background-size: calc(2*var(--s)) var(--s); +} + +html .app-shell.pattern-p22, +.pattern-p22 { + --s: 82px; /* control the size*/ + --c1: #b2b2b2; + --c2: #ffffff; + --c3: #d9d9d9; + + --_g: var(--c3) 0 120deg,#0000 0; + background: + conic-gradient(from -60deg at 50% calc(100%/3),var(--_g)), + conic-gradient(from 120deg at 50% calc(200%/3),var(--_g)), + conic-gradient(from 60deg at calc(200%/3),var(--c3) 60deg,var(--c2) 0 120deg,#0000 0), + conic-gradient(from 180deg at calc(100%/3),var(--c1) 60deg,var(--_g)), + linear-gradient(90deg,var(--c1) calc(100%/6),var(--c2) 0 50%, + var(--c1) 0 calc(500%/6),var(--c2) 0); + background-size: calc(1.732*var(--s)) var(--s); +} + +html .app-shell.pattern-p23, +.pattern-p23 { + --s: 50px; /* control the size*/ + --c1: #0b2e59; + --c2: #0d6759; + + background: + conic-gradient(at 25% 25%,#0000 75%,var(--c1) 0) var(--s) var(--s), + repeating-conic-gradient(at 25% 25%,var(--c1) 0 25%,var(--c2) 0 50%); + background-size: calc(2*var(--s)) calc(2*var(--s)); +} + +html .app-shell.pattern-p24, +.pattern-p24 { + --s: 100px; /* control the size*/ + --c1: #1bb0ce; + --c2: #dad6ca; + + --_g1: + var(--c1) calc(25%/3) ,#0000 0 calc(50%/3), + var(--c1) 0 25% ,#0000 0 75%, + var(--c1) 0 calc(250%/3),#0000 0 calc(275%/3), + var(--c1) 0; + --_g2: + #0000 calc(25%/3) ,var(--c1) 0 calc(50%/3), + #0000 0 calc(250%/3),var(--c1) 0 calc(275%/3), + #0000 0; + background: + linear-gradient( 45deg,var(--_g2)),linear-gradient( 45deg,var(--_g1)), + linear-gradient(-45deg,var(--_g2)),linear-gradient(-45deg,var(--_g1)) + var(--c2); /* second color */ + background-position: 0 0,var(--s) var(--s); + background-size: calc(2*var(--s)) calc(2*var(--s)); +} + +html .app-shell.pattern-p25, +.pattern-p25 { + --s: 100px; /* control the size*/ + --c1: #c3ccaf; + --c2: #67434f; + + --_s: calc(2*var(--s)) calc(2*var(--s)); + --_g: var(--_s) conic-gradient(at 40% 40%,#0000 75%,var(--c1) 0); + --_p: var(--_s) conic-gradient(at 20% 20%,#0000 75%,var(--c2) 0); + background: + calc( .9*var(--s)) calc( .9*var(--s))/var(--_p), + calc(-.1*var(--s)) calc(-.1*var(--s))/var(--_p), + calc( .7*var(--s)) calc( .7*var(--s))/var(--_g), + calc(-.3*var(--s)) calc(-.3*var(--s))/var(--_g), + conic-gradient(from 90deg at 20% 20%,var(--c2) 25%,var(--c1) 0) + 0 0/var(--s) var(--s); +} + +html .app-shell.pattern-p26, +.pattern-p26 { + --s: 106px; /* control the size*/ + --c1: #eafde6; + --c2: #519548; + + background: + conic-gradient(from 30deg at 87.5% 75%,var(--c1) 60deg,var(--c2) 0 120deg,#0000 0) 0 calc(.2165*var(--s)), + conic-gradient(from -90deg at 50% 25%,var(--c2) 60deg,var(--c1) 0 180deg,#0000 0), + conic-gradient(from 90deg at 50% 75%,var(--c2) 120deg,var(--c1) 0 180deg,#0000 0), + conic-gradient(from -30deg at 87.5% 50%,var(--c2) 120deg,var(--c1) 0 240deg,#0000 0), + conic-gradient(from 90deg at 37.5% 50%,var(--c2) 120deg,var(--c1) 0 180deg,var(--c2) 0 240deg,var(--c1) 0); + background-size: var(--s) calc(.866*var(--s)); +} + +html .app-shell.pattern-p27, +.pattern-p27 { + --s: 60px; /* control the size*/ + --c1: #b09f79; + --c2: #476074; + + --_g: #0000 83%,var(--c1) 85% 99%,#0000 101%; + background: + radial-gradient(27% 29% at right ,var(--_g)) calc(var(--s)/ 2) var(--s), + radial-gradient(27% 29% at left ,var(--_g)) calc(var(--s)/-2) var(--s), + radial-gradient(29% 27% at top ,var(--_g)) 0 calc(var(--s)/ 2), + radial-gradient(29% 27% at bottom,var(--_g)) 0 calc(var(--s)/-2) + var(--c2); + background-size: calc(2*var(--s)) calc(2*var(--s)); +} + +html .app-shell.pattern-p28, +.pattern-p28 { + --s: 80px; /* control the size*/ + --c1: #5e8c6a; + --c2: #bfb35a; + + --_s: calc(2*var(--s)) calc(2*var(--s)); + --_g: 35.36% 35.36% at; + --_c: #0000 66%,var(--c2) 68% 70%,#0000 72%; + background: + radial-gradient(var(--_g) 100% 25%,var(--_c)) var(--s) var(--s)/var(--_s), + radial-gradient(var(--_g) 0 75%,var(--_c)) var(--s) var(--s)/var(--_s), + radial-gradient(var(--_g) 100% 25%,var(--_c)) 0 0/var(--_s), + radial-gradient(var(--_g) 0 75%,var(--_c)) 0 0/var(--_s), + repeating-conic-gradient(var(--c1) 0 25%,#0000 0 50%) 0 0/var(--_s), + radial-gradient(var(--_c)) 0 calc(var(--s)/2)/var(--s) var(--s) + var(--c1); +} + +html .app-shell.pattern-p29, +.pattern-p29 { + --s: 150px; /* control the size*/ + --c1: #ccbf82; + --c2: #604848; + + --_g: var(--c1) 6.1%,var(--c2) 6.4% 18.6%,var(--c1) 18.9% 31.1%, + var(--c2) 31.4% 43.6%,var(--c1) 43.9% 56.1%,var(--c2) 56.4% 68.6%,#0000 68.9%; + background: + radial-gradient(var(--s) at 100% 0 ,var(--_g)), + radial-gradient(var(--s) at 0 0 ,var(--_g)), + radial-gradient(var(--s) at 0 100%,var(--_g)), + radial-gradient(var(--s) at 100% 100%,var(--_g)) var(--c1); + background-size: var(--s) var(--s); +} + +html .app-shell.pattern-p30, +.pattern-p30 { + --s: 150px; /* control the size*/ + --c1: #046d8b; + --c2: #2fb8ac; + + --_g: #0000 90deg,var(--c1) 0; + background: + conic-gradient(from 116.56deg at calc(100%/3) 0 ,var(--_g)), + conic-gradient(from -63.44deg at calc(200%/3) 100%,var(--_g)) + var(--c2); + background-size: var(--s) var(--s); +} + +html .app-shell.pattern-p31, +.pattern-p31 { + --s: 60px; /* control the size*/ + --c1: #ffdc56; + --c2: #fe6601; + --c3: #803201; + + --_s: calc(2*var(--s)) calc(2*var(--s)); + --_g1: var(--_s) conic-gradient(at calc(500%/6) 50%,var(--c3) 25%,#0000 0); + --_g2: var(--_s) conic-gradient(at calc(200%/3) 50%,var(--c2) 25%,#0000 0); + background: + var(--s) var(--s)/var(--_g1),0 0/var(--_g1), + var(--s) var(--s)/var(--_g2),0 0/var(--_g2), + repeating-conic-gradient(var(--c1) 0 25%,#0000 0 50%) 0 0/var(--_s), + linear-gradient(var(--c1) calc(100%/3),var(--c2) 0 calc(200%/3),var(--c3) 0) + 0 0/var(--s) var(--s); +} + +html .app-shell.pattern-p32, +.pattern-p32 { + --s: 50px; /* control the size*/ + --c1: #6963bb; + --c2: #99ffc0; + --c3: #ffd6d6; + + --_g: conic-gradient(at 50% 25%,#0000 75%,var(--c1) 0); + background: + var(--_g),var(--_g) var(--s) var(--s), + var(--_g) calc(2*var(--s)) calc(2*var(--s)), + var(--_g) calc(3*var(--s)) calc(3*var(--s)), + repeating-linear-gradient(135deg,var(--c2) 0 12.5%,var(--c3) 0 25%); + background-size: calc(4*var(--s)) calc(4*var(--s)) +} + +html .app-shell.pattern-p33, +.pattern-p33 { + --s: 100px; /* control the size*/ + --c1: #668284; + --c2: #b6d8c0; + --c3: #b9d7d9; + + --_g: #0000, var(--c1) 1deg 30deg,var(--c2) 31deg 89deg,var(--c1) 90deg 119deg,#0000 120deg; + background: + conic-gradient(from -60deg at 50% 28.86%,var(--_g)), + conic-gradient(from 30deg at 71.14% 50%,var(--_g)), + conic-gradient(from 120deg at 50% 71.14%,var(--_g)), + conic-gradient(from 210deg at 28.86% 50%,var(--_g)) + var(--c3); + background-size: var(--s) var(--s); +} + +html .app-shell.pattern-p34, +.pattern-p34 { + --s: 100px; /* control the size*/ + --c1: #fcd036; + --c2: #987f69; + + --_g: var(--c1) 0 100%,#0000 102%; + background: + conic-gradient(#0000 75%,var(--_g)) calc(var(--s)/4) calc(var(--s)/4), + radial-gradient(65% 65% at 50% -50%,var(--_g)), + radial-gradient(65% 65% at -50% 50%,var(--_g)), + radial-gradient(65% 65% at 50% 150%,var(--_g)), + radial-gradient(65% 65% at 150% 50%,var(--_g)) + var(--c2); + background-size: var(--s) var(--s); +} + +html .app-shell.pattern-p35, +.pattern-p35 { + --s: 76px; /* control the size*/ + --c1: #1a144a; + --c2: #2eb044; + + --_g: conic-gradient(var(--c1) 25%,#0000 0) 0 0; + background: + var(--_g)/calc(2*var(--s)) calc(var(--s)/9.5), + var(--_g)/calc(var(--s)/9.5) calc(2*var(--s)), + repeating-conic-gradient(#0000 0 25%,var(--c1) 0 50%) + var(--s) 0 /calc(2*var(--s)) calc(2*var(--s)), + radial-gradient(50% 50%,var(--c2) 98%,var(--c1)) + 0 0/var(--s) var(--s); +} + +html .app-shell.pattern-p36, +.pattern-p36 { + --s: 70px; /* control the size*/ + --c1: #6b5344; + --c2: #f8ecc9; + + --_l: #0000 46%,var(--c1) 47% 53%,#0000 54%; + background: + radial-gradient(100% 100% at 100% 100%,var(--_l)) var(--s) var(--s), + radial-gradient(100% 100% at 0 0 ,var(--_l)) var(--s) var(--s), + radial-gradient(100% 100%,#0000 22%,var(--c1) 23% 29%, #0000 30% 34%,var(--c1) 35% 41%,#0000 42%), + var(--c2); + background-size: calc(var(--s)*2) calc(var(--s)*2); +} + +html .app-shell.pattern-p37, +.pattern-p37 { + --s: 80px; /* control the size*/ + --c1: #542437; + --c2: #c02942; + + --_g: + #0000 calc(-650%/13) calc(50%/13),var(--c1) 0 calc(100%/13), + #0000 0 calc(150%/13),var(--c1) 0 calc(200%/13), + #0000 0 calc(250%/13),var(--c1) 0 calc(300%/13); + --_g0: repeating-linear-gradient( 45deg,var(--_g)); + --_g1: repeating-linear-gradient(-45deg,var(--_g)); + background: + var(--_g0),var(--_g0) var(--s) var(--s), + var(--_g1),var(--_g1) var(--s) var(--s) var(--c2); + background-size: calc(2*var(--s)) calc(2*var(--s)); +} + +html .app-shell.pattern-p38, +.pattern-p38 { + --s: 56px; /* control the size*/ + --c1: #3fb8af; + --c2: #ff9e9d; + + --_c1: var(--c1) 99%,#0000 101%; + --_c2: var(--c2) 99%,#0000 101%; + + --r:calc(var(--s)*.866); + --g0:radial-gradient(var(--s),var(--_c1)); + --g1:radial-gradient(var(--s),var(--_c2)); + --f:radial-gradient(var(--s) at calc(100% + var(--r)) 50%,var(--_c1)); + --p:radial-gradient(var(--s) at 100% 50%,var(--_c2)); + background: + var(--f) 0 calc(-5*var(--s)/2), + var(--f) calc(-2*var(--r)) calc(var(--s)/2), + var(--p) 0 calc(-2*var(--s)), + var(--g0) var(--r) calc(-5*var(--s)/2), + var(--g1) var(--r) calc( 5*var(--s)/2), + radial-gradient(var(--s) at 100% 100%,var(--_c1)) 0 calc(-1*var(--s)), + radial-gradient(var(--s) at 0% 50% ,var(--_c1)) 0 calc(-4*var(--s)), + var(--g1) calc(-1*var(--r)) calc(-7*var(--s)/2), + var(--g0) calc(-1*var(--r)) calc(-5*var(--s)/2), + var(--p) calc(-2*var(--r)) var(--s), + var(--g0) calc(-1*var(--r)) calc(var(--s)/ 2), + var(--g1) calc(-1*var(--r)) calc(var(--s)/-2), + var(--g0) 0 calc(-1*var(--s)), + var(--g1) var(--r) calc(var(--s)/-2), + var(--g0) var(--r) calc(var(--s)/ 2) + var(--c2); + background-size: calc(4*var(--r)) calc(6*var(--s)); +} + +html .app-shell.pattern-p39, +.pattern-p39 { + --s: 50px; /* control the size*/ + --c1: #cff09e; + --c2: #0b486b; + + --_g: radial-gradient(var(--c2) 49%,#0000 50%); + background: + var(--_g) calc(var(--s)/-2) calc(var(--s)/2), + repeating-conic-gradient(from 45deg,var(--c1) 0 25%,#0000 0 50%) + calc(var(--s)/2) calc(var(--s)/2), + var(--_g),var(--_g) var(--s) var(--s) var(--c1); + background-size: calc(2*var(--s)) calc(2*var(--s)); +} + +html .app-shell.pattern-p40, +.pattern-p40 { + --s: 75px; /* control the size*/ + --c1: #1fffa9; + --c2: #a31e39; + --c3: #31570e; + + --_c:#0000,var(--c1) 1deg 79deg,#0000 81deg; + --g0:conic-gradient(from 140deg at 50% 87.5% ,var(--_c)); + --g1:conic-gradient(from 140deg at 50% 81.25%,var(--_c)); + --g2:conic-gradient(from 140deg at 50% 75% ,var(--_c)); + --g3:conic-gradient(at 10% 20%,#0000 75%,var(--c1) 0); + background: + var(--g0) 0 calc(var(--s)/-4),var(--g0) var(--s) calc(3*var(--s)/4), + var(--g1) ,var(--g1) var(--s) var(--s), + var(--g2) 0 calc(var(--s)/ 4),var(--g2) var(--s) calc(5*var(--s)/4), + var(--g3) calc( var(--s)/-10) var(--s), + var(--g3) calc(9*var(--s)/10) calc(2*var(--s)), + repeating-conic-gradient(from 45deg,var(--c2) 0 25%,var(--c3) 0 50%); + background-size: calc(2*var(--s)) calc(2*var(--s)); +} + +html .app-shell.pattern-p41, +.pattern-p41 { + --s: 150px; /* control the size*/ + --c1: #5e412f; + --c2: #fcebb6; + + background: + linear-gradient(135deg,#0000 18.75%,var(--c1) 0 31.25%,#0000 0), + repeating-linear-gradient(45deg,var(--c1) -6.25% 6.25%,var(--c2) 0 18.75%); + background-size: var(--s) var(--s); +} + +html .app-shell.pattern-p42, +.pattern-p42 { + --s: 120px; /* control the size*/ + --c1: #1a2030; + --c2: #0f9177; + --c3: #fdebad; + --c4: #d34434; + --c5: #b5d999; + + --_g: radial-gradient(#0000 70%,var(--c1) 71%); + background: + var(--_g),var(--_g) calc(var(--s)/2) calc(var(--s)/2), + conic-gradient(var(--c2) 25%,var(--c3) 0 50%,var(--c4) 0 75%,var(--c5) 0); + background-size: var(--s) var(--s); +} + +html .app-shell.pattern-p43, +.pattern-p43 { + --s: 34px; /* control the size*/ + --c1: #ecbe13; + --c2: #309292; + + --_g: radial-gradient(calc(var(--s)/2),var(--c1) 97%,#0000); + background: + var(--_g),var(--_g) calc(2*var(--s)) calc(2*var(--s)), + repeating-conic-gradient(from 45deg,#0000 0 25%,var(--c2) 0 50%) calc(-.707*var(--s)) calc(-.707*var(--s)), + repeating-linear-gradient(135deg,var(--c1) calc(var(--s)/-2) calc(var(--s)/2),var(--c2) 0 calc(2.328*var(--s))); + background-size: calc(4*var(--s)) calc(4*var(--s)); + + + +} + +html .app-shell.pattern-p44, +.pattern-p44 { + --s: 60px; /* control the size*/ + --c1: #7bb0a8; + --c2: #a7dbab; + + --_c: var(--c1) calc(100% - var(--s)/2) 99%,#0000; + --_g: var(--s),#0000 calc(99% - var(--s)/2),var(--_c); + background: + radial-gradient(var(--s) at 100% var(--_g)), + radial-gradient(calc(var(--s)/4) at 50% calc(100%/3),var(--_c)) var(--s) 0, + radial-gradient(var(--s) at 0% var(--_g)) 0 calc(3*var(--s)) + var(--c2); + background-size: + calc(2*var(--s)) calc(9*var(--s)/4), + calc(2*var(--s)) calc(3*var(--s)/4); + +} + +html .app-shell.pattern-p45, +.pattern-p45 { + --s: 30px; /* control the size*/ + --c1: #e5fcc2; + --c2: #45ada8; + + --_s:37.5% 12.5% at 62.5%; + --_g:34% 99%,#0000 101%; + --g1:radial-gradient(var(--_s) 100%,#0000 32%,var(--c1) var(--_g)); + --g2:radial-gradient(var(--_s) 0 ,#0000 32%,var(--c1) var(--_g)); + --g3:radial-gradient(var(--_s) 100%,#0000 32%,var(--c2) var(--_g)); + --g4:radial-gradient(var(--_s) 0 ,#0000 32%,var(--c2) var(--_g)); + background: + var(--g1), + var(--g2) 0 calc(3*var(--s)), + var(--g3) var(--s) calc(3*var(--s)), + var(--g4) var(--s) calc(6*var(--s)), + var(--g1) calc(2*var(--s)) calc(6*var(--s)), + var(--g2) calc(2*var(--s)) calc(9*var(--s)), + var(--g3) calc(3*var(--s)) calc(9*var(--s)), + var(--g4) calc(3*var(--s)) 0, + repeating-linear-gradient(var(--c1) 0 25%,var(--c2) 0 50%); + background-size: calc(4*var(--s)) calc(12*var(--s)); + +} + +html .app-shell.pattern-p46, +.pattern-p46 { + --s: 65px; /* control the size*/ + --c1: #ecd078; + --c2: #0b486b; + + --g: calc(var(--s)/5); /* control the gap */ + --_l: #0000 calc(33% - .866*var(--g)),var(--c1) calc(33.2% - .866*var(--g)) 33%,#0000 34%; + background: + repeating-linear-gradient(var(--c1) 0 var(--g), #0000 0 50%) + 0 calc(.866*var(--s) - var(--g)/2), + conic-gradient(from -150deg at var(--g) 50%,var(--c1) 120deg,#0000 0), + linear-gradient(-120deg,var(--_l)),linear-gradient( -60deg,var(--_l)) + var(--c2); + background-size: var(--s) calc(3.466*var(--s)) + +} + +html .app-shell.pattern-p47, +.pattern-p47 { + --s: 100px; /* control the size*/ + --c1: #f2e9e1; + --c2: #99b2b7; + + background: + repeating-conic-gradient(var(--c1) 0 45deg,var(--c2) 0 90deg) + 0/var(--s) var(--s); + +} + +html .app-shell.pattern-p48, +.pattern-p48 { + --s: 50px; /* control the size*/ + --c1: #d9ceb2; + --c2: #948c75; + + --_g: calc(2*var(--s)) calc(2*var(--s)) + radial-gradient(25% 25%,var(--c1) 99%,#0000 101%); + background: + 0 var(--s)/var(--_g),var(--s) 0/var(--_g), + radial-gradient(50% 50%,var(--c2) 97%,#0000) + calc(var(--s)/2) calc(var(--s)/2)/var(--s) var(--s), + linear-gradient(90deg,var(--c1) 50%,var(--c2) 0) + 0 0/calc(2*var(--s)); + +} + +html .app-shell.pattern-p49, +.pattern-p49 { + --s: 100px; /* control the size*/ + --c1: #5a3d31; + --c2: #e5edb8; + + background: + radial-gradient(#0000 50%,var(--c1) 52% 55%,var(--c2) 57%) + 0 0/var(--s) var(--s), + repeating-linear-gradient(45deg,var(--c1) 0 25%,var(--c2) 0 50%) + 0 0/calc(2*var(--s)) calc(2*var(--s)) + +} + +html .app-shell.pattern-p50, +.pattern-p50 { + --s: 50px; /* control the size*/ + --c1: #e08e79; + --c2: #774f38; + + --c: var(--c1) 0 25%,var(--c2) 0 50%,#0000 0; + background: + conic-gradient(from 180deg,var(--c)) + 0/var(--s) var(--s), + repeating-conic-gradient(from 90deg,var(--c)) + 0/calc(3*var(--s)) calc(3*var(--s)); + +} + +html .app-shell.pattern-p51, +.pattern-p51 { + --s: 100px; /* control the size*/ + --c1: #cc2a41; + --c2: #351330; + + --_g: #0000 24%, + var(--c2) 26% 34%,var(--c1) 36% 44%, + var(--c2) 46% 54%,var(--c1) 56% 64%, + var(--c2) 66% 74%,#0000 76%; + background: + radial-gradient(100% 100% at 100% 0,var(--_g)), + radial-gradient(100% 100% at 0 100%,var(--_g)), + radial-gradient(var(--c2) 14%,var(--c1) 16%) + calc(var(--s)/2) calc(var(--s)/2); + background-size: var(--s) var(--s); + +} + +html .app-shell.pattern-p52, +.pattern-p52 { + --s: 29px; /* control the size*/ + --c1: #615375; + --c2: #8eb2c5; + + --g:#0000 calc(100% - var(--s)/3 - 1px),var(--c1) + calc(100% - var(--s)/3) calc(100% - 1px),#0000; + --r:calc(2.414*var(--s)); + --p:calc(1.414*var(--s)); + background: + radial-gradient(var(--r) at 0 0 ,var(--g)) var(--r) var(--r), + radial-gradient(var(--s) at 0 0 ,var(--g)) var(--p) var(--p), + radial-gradient(var(--r) at 0 100%,var(--g)) 0 var(--p), + radial-gradient(var(--s) at 0 100%,var(--g)) calc(-1*var(--s)) var(--r), + radial-gradient(var(--r) at 100% 0 ,var(--g)) var(--p) 0, + radial-gradient(var(--s) at 100% 0 ,var(--g)) var(--r) calc(-1*var(--s)), + radial-gradient(var(--r) at 100% 100%,var(--g)) calc(-1*var(--s)) calc(-1*var(--s)), + radial-gradient(var(--s) at 100% 100%,var(--g)) var(--c2); + background-size: calc(2*var(--r)) calc(2*var(--r)); + +} + +html .app-shell.pattern-p53, +.pattern-p53 { + --s: 160px; /* control the size*/ + --c1: #a14016; + --c2: #cfc89a; + + --_g: var(--s) var(--s) + radial-gradient(var(--c1) 17%,var(--c2) 18% 35%,#0000 36.5%); + background: + calc(var(--s)/-4) calc(var(--s)/-4)/var(--_g), + calc(var(--s)/ 4) calc(var(--s)/ 4)/var(--_g), + radial-gradient(var(--c2) 34%,var(--c1) 36% 68%,#0000 70%) + 0 0/calc(var(--s)/2) calc(var(--s)/2), + repeating-linear-gradient(45deg,var(--c1) -12.5% 12.5%,var(--c2) 0 37.5%) + 0 0/var(--s) var(--s); + +} + +html .app-shell.pattern-p54, +.pattern-p54 { + --s: 100px; /* control the size*/ + --c1: #ffd500; + --c2: #0341ae; + + background: + conic-gradient(at 50% 25%,var(--c2) 25%,#0000 0) + calc(var(--s)/-4) calc(var(--s)/-2), + conic-gradient(at 25% 25%,var(--c1) 25%,#0000 0 75%,var(--c2) 0) + calc(var(--s)/4) calc(var(--s)/-4), + conic-gradient(from -90deg at 75% 25%,var(--c2) 75%,var(--c1) 0); + background-size: var(--s) var(--s); + +} + +html .app-shell.pattern-p55, +.pattern-p55 { + --s: 50px; /* control the size*/ + --c1: #292522; + --c2: #efd9b4; + + --c: #0000 75%,var(--c1) 0; + --g1: conic-gradient(at 75% 25%,var(--c)); + --g2: conic-gradient(at 25% 75%,var(--c)); + background: + var(--g1),var(--g1) var(--s) var(--s), + var(--g2),var(--g2) var(--s) var(--s) var(--c2); + background-size: calc(2*var(--s)) calc(2*var(--s)); + +} + +html .app-shell.pattern-p56, +.pattern-p56 { + --s: 6px; /* control the size*/ + --c1: #3b2d38; + --c2: #f02475; + + --g:#0000 66%,var(--c1) 68% 98%,#0000; + background: + radial-gradient(30% 30% at 0% 30%,var(--g)) + var(--s) calc(9*var(--s)), + radial-gradient(30% 30% at 100% 30%,var(--g)) + var(--s) calc(-1*var(--s)), + radial-gradient(30% 30% at 30% 100%,var(--g)) + calc(10*var(--s)) 0, + radial-gradient(30% 30% at 30% 0% ,var(--g)) + var(--c2); + background-size: calc(20*var(--s)) calc(20*var(--s)); + +} + +html .app-shell.pattern-p57, +.pattern-p57 { + --s: 194px; /* control the size*/ + --c1: #f6edb3; + --c2: #acc4a3; + --c3: #55897c; + + --_l:#0000 calc(25%/3),var(--c1) 0 25%,#0000 0; + --_g:conic-gradient(from 120deg at 50% 87.5%,var(--c1) 120deg,#0000 0); + background: + var(--_g),var(--_g) 0 calc(var(--s)/2), + conic-gradient(from 180deg at 75%,var(--c2) 60deg,#0000 0), + conic-gradient(from 60deg at 75% 75%,var(--c1) 0 60deg,#0000 0), + linear-gradient(150deg,var(--_l)) 0 calc(var(--s)/2), + conic-gradient(at 25% 25%,#0000 50%,var(--c2) 0 240deg,var(--c1) 0 300deg,var(--c2) 0), + linear-gradient(-150deg,var(--_l)) var(--c3); + background-size: calc(0.866*var(--s)) var(--s); + +} diff --git a/frontend/src/lib/theme.ts b/frontend/src/lib/theme.ts new file mode 100644 index 0000000..53fbe5b --- /dev/null +++ b/frontend/src/lib/theme.ts @@ -0,0 +1,61 @@ +// Theme helpers — derive subtle pattern shades from a user's chosen color +// swatch. Patterns in theme-patterns.css read --c1..--c4; we override them +// with hsl variations of the user's hue so the background stays subtle. + +export function hexToHsl(hex: string): { h: number; s: number; l: number } { + const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim()); + if (!m) return { h: 243, s: 75, l: 66 }; // indigo fallback + const n = parseInt(m[1], 16); + const r = ((n >> 16) & 255) / 255; + const g = ((n >> 8) & 255) / 255; + const b = (n & 255) / 255; + const max = Math.max(r, g, b); + const min = Math.min(r, g, b); + const l = (max + min) / 2; + if (max === min) return { h: 0, s: 0, l: Math.round(l * 100) }; + const d = max - min; + const s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + let h = 0; + if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6; + else if (max === g) h = ((b - r) / d + 2) / 6; + else h = ((r - g) / d + 4) / 6; + return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) }; +} + +// Four subtle shades built on the swatch's hue — pastel tints distinct +// enough that the pattern motif reads against the near-white app shell, +// but soft enough to stay a background wash rather than a loud print. +// `alpha` (0..1) fades the whole motif toward the shell background — the +// app-shell keeps `background: whitesmoke` underneath, so lowering it +// blends the pattern out. +export function themeShades( + hex: string, + alpha = 1 +): { c1: string; c2: string; c3: string; c4: string } { + const { h } = hexToHsl(hex); + return { + c1: `hsl(${h} 45% 93% / ${alpha})`, + c2: `hsl(${h} 50% 85% / ${alpha})`, + c3: `hsl(${h} 55% 74% / ${alpha})`, + c4: `hsl(${(h + 40) % 360} 50% 87% / ${alpha})` + }; +} + +// Pattern ids: 'p1'..'p60', '' = none. Currently showing the first 19; +// bump this to include more (the CSS file still ships all 60). +export const PATTERN_COUNT = 19; +export function patternId(n: number): string { + return `p${n}`; +} + +// Pattern size slider — the value is the pattern's `--s` in vw units, so the +// motif scales with the viewport. E.g. 7 at a 1400px screen ≈ 98px tiles. +export const THEME_SIZE_MIN = 2; +export const THEME_SIZE_MAX = 16; +export const THEME_SIZE_DEFAULT = 7; + +// Pattern opacity slider — 1 = current full visibility, down to a faint hint. +export const THEME_OPACITY_MIN = 0.05; +export const THEME_OPACITY_MAX = 1; +export const THEME_OPACITY_STEP = 0.05; +export const THEME_OPACITY_DEFAULT = 1; diff --git a/frontend/src/routes/[fam]/+layout.server.ts b/frontend/src/routes/[fam]/+layout.server.ts index 57035f5..0e76164 100644 --- a/frontend/src/routes/[fam]/+layout.server.ts +++ b/frontend/src/routes/[fam]/+layout.server.ts @@ -98,6 +98,9 @@ export async function load(event) { userId: session.id, memberName: session.name, memberColor: session.color || '', + memberPattern: session.pattern || '', + memberThemeSize: session.themeSize || '', + memberThemeOpacity: session.themeOpacity || '', username: session.username || '', role: session.role } diff --git a/frontend/src/routes/[fam]/+layout.svelte b/frontend/src/routes/[fam]/+layout.svelte index eaeef3e..738a751 100644 --- a/frontend/src/routes/[fam]/+layout.svelte +++ b/frontend/src/routes/[fam]/+layout.svelte @@ -9,9 +9,26 @@ import { Sidebar, TopNav, Footer, Chat } from '$lib/components'; import { chatIcon } from '$lib/components/icons'; import { recordShortcut } from '$lib/shortcut'; + import { themeShades } from '$lib/theme'; + import '$lib/theme-patterns.css'; + import { themeDraft } from '$lib/stores/theme.svelte'; import type { Session } from '$lib/types'; let { children, data } = $props(); + + // Background pattern ('' = none) tinted with the user's colour. A live + // draft (clicked in the picker, not yet saved) previews immediately; + // otherwise the saved value from the session applies. + let bgPattern = $derived(themeDraft.pattern ?? (data.session?.memberPattern || '')); + let bgColor = $derived(themeDraft.color ?? (data.session?.memberColor || '#6366f1')); + let bgOpacity = $derived( + themeDraft.opacity ?? + (Number(data.session?.memberThemeOpacity) > 0 ? data.session?.memberThemeOpacity : 1) + ); + let bgShades = $derived(themeShades(bgColor, Number(bgOpacity) || 1)); + // Pattern size in vw units ('' = untouched → pattern class default). + let bgSize = $derived(themeDraft.size ?? (data.session?.memberThemeSize || '')); + let bgSizeVw = $derived(Number(bgSize) > 0 ? bgSize : ''); let session = $state(data.session); let isParent = $state(data.isParent); let role = $state(data.role); @@ -177,7 +194,10 @@
-
+
= {}; if (name) data.name = name; if (color) data.color = color; + data.pattern = pattern; + data.themeSize = themeSize; + data.themeOpacity = themeOpacity; data.email = email || ''; const me = await s.fam.updateProfile(famId, data); - return { success: true, name: me.name, color: me.color, email: me.email }; + return { + success: true, + name: me.name, + color: me.color, + pattern: me.pattern, + themeSize: me.themeSize, + themeOpacity: me.themeOpacity, + email: me.email + }; } catch (e) { return { error: e instanceof Error ? e.message : 'Update failed' }; } @@ -99,8 +119,19 @@ export const actions = { const data: Record = {}; if (name) data.name = name; if (color) data.color = color; + data.pattern = pattern; + data.themeSize = themeSize; + data.themeOpacity = themeOpacity; const me = await s.fam.updateProfile(famId, data); - return { success: true, name: me.name, color: me.color, email: '' }; + return { + success: true, + name: me.name, + color: me.color, + pattern: me.pattern, + themeSize: me.themeSize, + themeOpacity: me.themeOpacity, + email: '' + }; } catch (e) { return { error: e instanceof Error ? e.message : 'Update failed' }; } diff --git a/frontend/src/routes/[fam]/[username]/preferences/+page.svelte b/frontend/src/routes/[fam]/[username]/preferences/+page.svelte index d539315..e72efde 100644 --- a/frontend/src/routes/[fam]/[username]/preferences/+page.svelte +++ b/frontend/src/routes/[fam]/[username]/preferences/+page.svelte @@ -1,12 +1,20 @@