add theme patterns

This commit is contained in:
JCEEE
2026-09-10 13:23:55 +01:00
parent 27492b1558
commit a270d965fe
13 changed files with 1429 additions and 27 deletions
+4 -1
View File
@@ -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;
@@ -0,0 +1,184 @@
<script lang="ts">
import '../theme-patterns.css';
import {
themeShades,
PATTERN_COUNT,
patternId,
THEME_SIZE_MIN,
THEME_SIZE_MAX,
THEME_SIZE_DEFAULT,
THEME_OPACITY_MIN,
THEME_OPACITY_MAX,
THEME_OPACITY_STEP,
THEME_OPACITY_DEFAULT
} from '$lib/theme';
let {
value = '',
color = '#6366f1',
size = THEME_SIZE_DEFAULT,
opacity = THEME_OPACITY_DEFAULT,
onchange,
onsize,
onopacity
}: {
value?: string;
color?: string;
size?: number;
opacity?: number;
onchange: (v: string) => void;
onsize?: (v: number) => void;
onopacity?: (v: number) => void;
} = $props();
let shades = $derived(themeShades(color));
// Inline vars beat the pattern class defaults; --s shrinks the pattern so
// thumbnails show a sample of the tiling.
let vars = $derived(
`--c1:${shades.c1};--c2:${shades.c2};--c3:${shades.c3};--c4:${shades.c4};--s:26px`
);
// Local slider state — bound directly to the inputs (no controlled-value
// re-render mid-drag), then emitted on input for the live preview.
let sizeVal = $state(size);
let opacityVal = $state(opacity);
// Keep local state in sync if the props ever change (e.g. after a save).
$effect(() => {
sizeVal = size;
});
$effect(() => {
opacityVal = opacity;
});
</script>
<div class="pattern-grid">
<button
type="button"
class="pattern-tile none"
class:selected={value === ''}
title="No background pattern"
onclick={() => onchange('')}
>
<span class="none-dot" style="background:{color}"></span>
</button>
{#each Array(PATTERN_COUNT) as _, i}
{@const id = patternId(i + 1)}
<button
type="button"
class="pattern-tile pattern-{id}"
class:selected={value === id}
title={`Pattern ${i + 1}`}
style={vars}
onclick={() => onchange(id)}
></button>
{/each}
</div>
{#if value !== ''}
{#if onsize}
<div class="slider-row">
<div class="slider-head">
<label for="theme-size">Pattern size</label>
<span class="slider-val">{sizeVal}</span>
</div>
<input
id="theme-size"
type="range"
min={THEME_SIZE_MIN}
max={THEME_SIZE_MAX}
step="0.5"
bind:value={sizeVal}
oninput={() => onsize?.(sizeVal)}
/>
<span class="slider-hint">scales with screen width</span>
</div>
{/if}
{#if onopacity}
<div class="slider-row">
<div class="slider-head">
<label for="theme-opacity">Opacity</label>
<span class="slider-val">{Math.round(opacityVal * 100)}%</span>
</div>
<input
id="theme-opacity"
type="range"
min={THEME_OPACITY_MIN}
max={THEME_OPACITY_MAX}
step={THEME_OPACITY_STEP}
bind:value={opacityVal}
oninput={() => onopacity?.(opacityVal)}
/>
<span class="slider-hint">max = current look</span>
</div>
{/if}
{/if}
<style>
.pattern-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
gap: 0.5rem;
margin-bottom: 0.5rem;
/* Static grid — currently 19 tiles fit without scrolling. Padding keeps
the selected tile's outline from clipping at the top-left corner. */
padding: 0.4rem;
}
.pattern-tile {
aspect-ratio: 1;
border-radius: 8px;
border: 1px solid #e5e7eb;
cursor: pointer;
padding: 0;
/* No background here — the imported .pattern-pN classes supply it.
A local background (0,2,0) would beat .pattern-pN (0,1,0) and hide
every preview. */
transition: outline 0.12s ease;
}
.pattern-tile:hover {
border-color: #9ca3af;
}
.pattern-tile.selected {
outline: 3px solid #111827;
outline-offset: 2px;
}
.pattern-tile.none {
display: flex;
align-items: center;
justify-content: center;
background: #fff;
}
.none-dot {
width: 14px;
height: 14px;
border-radius: 50%;
}
.slider-row {
margin-top: 0.75rem;
font-size: 0.8rem;
color: #374151;
}
.slider-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.3rem;
}
.slider-head label {
font-weight: 600;
}
.slider-row input[type='range'] {
width: 100%;
display: block;
accent-color: #6366f1;
}
.slider-val {
font-weight: 700;
color: #4338ca;
}
.slider-hint {
color: #9ca3af;
font-size: 0.72rem;
display: block;
margin-top: 0.15rem;
}
</style>
+1
View File
@@ -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';
+12
View File
@@ -146,6 +146,18 @@ async function ensureUsers(ids: Record<string, string>): Promise<void> {
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;
+22 -3
View File
@@ -69,7 +69,15 @@ export async function patchFam(pb: any, famId: string, body: Record<string, unkn
export async function getProfile(pb: any, famId: string, userId: string) {
const rec = await pb.collection('users').getOne(userId);
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 updateProfile(
@@ -81,9 +89,20 @@ export async function updateProfile(
const patch: Record<string, unknown> = {};
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
};
}
}
+4 -1
View File
@@ -5,4 +5,7 @@ export interface SessionUser {
role: 'parent' | 'child';
famId: string;
color?: string;
}
pattern?: string;
themeSize?: string;
themeOpacity?: string;
}
+14
View File
@@ -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
});
+991
View File
@@ -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);
}
+61
View File
@@ -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;
@@ -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
}
+21 -1
View File
@@ -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<Session | null>(data.session);
let isParent = $state(data.isParent);
let role = $state(data.role);
@@ -177,7 +194,10 @@
</script>
<div class="layout-stage" class:chat-open={chatStore.open}>
<div class="app-shell">
<div
class="app-shell {bgPattern ? `pattern-${bgPattern}` : ''}"
style={`${bgSizeVw ? `--s:calc(${bgSizeVw} * 1vw);` : ''}--c1:${bgShades.c1};--c2:${bgShades.c2};--c3:${bgShades.c3};--c4:${bgShades.c4}`}
>
<Sidebar {famName} session={data.session} {isParent} {role} />
<TopNav
role={data.session ? role : ''}
@@ -37,6 +37,9 @@ export async function load(event) {
famId,
memberName: me.name,
memberColor: me.color,
pattern: me.pattern || '',
themeSize: me.themeSize || '',
themeOpacity: me.themeOpacity || '',
email: me.email || '',
session: true
};
@@ -63,6 +66,9 @@ export async function load(event) {
famId,
memberName: session.name || '',
memberColor: session.color || '',
pattern: session.pattern || '',
themeSize: session.themeSize || '',
themeOpacity: session.themeOpacity || '',
email: '',
session: true
};
@@ -77,6 +83,9 @@ export const actions = {
const name = fd.get('name') as string;
const color = fd.get('color') as string;
const email = fd.get('email') as string;
const pattern = (fd.get('pattern') as string) || '';
const themeSize = (fd.get('themeSize') as string) || '';
const themeOpacity = (fd.get('themeOpacity') as string) || '';
const s = servicesFor(event);
const famId = session.famId;
@@ -86,9 +95,20 @@ export const actions = {
const data: Record<string, string> = {};
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<string, string> = {};
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' };
}
@@ -1,12 +1,20 @@
<script lang="ts">
import { enhance, applyAction } from '$app/forms';
import { ViewHeader, CardGrid, Card, Button } from '$lib/components';
import { invalidateAll } from '$app/navigation';
import { ViewHeader, CardGrid, Card, Button, PatternPicker } from '$lib/components';
import { themeDraft } from '$lib/stores/theme.svelte';
import { THEME_SIZE_DEFAULT, THEME_OPACITY_DEFAULT } from '$lib/theme';
let { data, form } = $props();
let memberName = $state(data.memberName || '');
let memberColor = $state(data.memberColor || '#6366f1');
let memberEmail = $state(data.email || '');
let memberPattern = $state(data.pattern || '');
let memberSize = $state(Number(data.themeSize) > 0 ? Number(data.themeSize) : THEME_SIZE_DEFAULT);
let memberOpacity = $state(
Number(data.themeOpacity) > 0 ? Number(data.themeOpacity) : THEME_OPACITY_DEFAULT
);
const colors = [
'#6366f1',
@@ -32,21 +40,33 @@
</CardGrid>
{:else}
<CardGrid>
<Card cols={2} title="Appearance">
<form
method="POST"
action="?/update"
use:enhance={() => {
return async ({ result }) => {
// Apply the action result (so the success/error message
// shows) WITHOUT resetting the form — the default enhance
// clears name/email inputs on a successful submit.
if (result.type === 'success' || result.type === 'failure') {
await applyAction(result);
}
};
}}
>
<!-- One form for appearance + theme; display:contents keeps the
cards as grid items. -->
<form
style="display:contents"
method="POST"
action="?/update"
use:enhance={() => {
return async ({ result }) => {
// Apply the action result (so the success/error message
// shows) WITHOUT resetting the form — the default enhance
// clears name/email inputs on a successful submit.
if (result.type === 'success' || result.type === 'failure') {
await applyAction(result);
}
// Refresh session.pattern so the app background updates, and drop
// the live draft — the saved value is authoritative from here on.
if (result.type === 'success') {
themeDraft.pattern = null;
themeDraft.color = null;
themeDraft.size = null;
themeDraft.opacity = null;
await invalidateAll();
}
};
}}
>
<Card cols={2} title="Appearance">
<div style="margin-bottom:1rem">
<label style="display:block;font-size:0.85rem;color:#374151;margin-bottom:0.25rem">
Display Name
@@ -71,7 +91,10 @@
style="width:2rem;height:2rem;border-radius:50%;border:{c === memberColor
? '3px solid #111827'
: '2px solid transparent'};background:{c};cursor:pointer"
onclick={() => (memberColor = c)}
onclick={() => {
memberColor = c;
themeDraft.color = c;
}}
></button>
{/each}
</div>
@@ -102,8 +125,40 @@
<div class="form-actions">
<Button type="submit" variant="primary">Update preferences</Button>
</div>
</form>
</Card>
</Card>
<Card cols={1} title="Theme">
<p class="theme-hint">
Pick a background pattern — it's coloured with your chosen colour. None keeps the plain
background.
</p>
<PatternPicker
value={themeDraft.pattern ?? memberPattern}
color={memberColor}
size={themeDraft.size ?? memberSize}
opacity={themeDraft.opacity ?? memberOpacity}
onchange={(v) => {
memberPattern = v;
// Live preview on the app background before saving.
themeDraft.pattern = v;
themeDraft.color = memberColor;
}}
onsize={(v) => {
memberSize = v;
// Live scale preview before saving.
themeDraft.size = v;
}}
onopacity={(v) => {
memberOpacity = v;
// Live fade preview before saving.
themeDraft.opacity = v;
}}
/>
<input type="hidden" name="pattern" value={themeDraft.pattern ?? memberPattern} />
<input type="hidden" name="themeSize" value={themeDraft.size ?? memberSize} />
<input type="hidden" name="themeOpacity" value={themeDraft.opacity ?? memberOpacity} />
</Card>
</form>
<Card cols={1} title="Account">
<p style="font-size:0.85rem;color:#6b7280">
@@ -122,4 +177,9 @@
padding-top: 1.25rem;
border-top: 1px solid #e5e7eb;
}
.theme-hint {
font-size: 0.85rem;
color: #6b7280;
margin: 0 0 0.75rem;
}
</style>