new feature: press F2 to cycle interface themes

Hunter
Tuesday, October 21st, 2025 at 5:42 PM

index.html+2-1
script.js+51 
style.css+115-28

index.html
1  <!DOCTYPE html>
  -<html lang="en">
2 +<html lang="en" data-theme="sky">
3  <head>
4  	<meta charset="UTF-8">
5  	<meta name="viewport" content="width=device-width, initial-scale=1.0">
6 +	<meta name="theme-color" content="#a3d5ff">
7  	<title>memori</title>
8  	<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>✂️</text></svg>">
9  	<link rel="stylesheet" href="style.css">
script.js
639  		});
640  	});
641  });
642 +
643 +// Theme system
644 +let currentThemeIndex = 0;
645 +let isF2Pressed = false;
646 +const themes = ['sea-breeze', 'grape-soda', 'coral', 'guac', 'mojito', 'toast'];
647 +
648 +function setTheme(theme) {
649 +	document.documentElement.setAttribute('data-theme', theme);
650 +	const backgroundColor = getComputedStyle(document.documentElement).getPropertyValue('--desk').trim();
651 +	document.querySelector('meta[name="theme-color"]').setAttribute('content', backgroundColor);
652 +}
653 +
654 +function cycleTheme() {
655 +	currentThemeIndex = (currentThemeIndex + 1) % themes.length;
656 +	const newTheme = themes[currentThemeIndex];
657 +	setTheme(newTheme);
658 +	saveThemeToLocalStorage(newTheme);
659 +}
660 +
661 +function saveThemeToLocalStorage(theme) {
662 +	localStorage.setItem('memori-theme', theme);
663 +}
664 +
665 +function loadThemeFromLocalStorage() {
666 +	const savedTheme = localStorage.getItem('memori-theme');
667 +	if (savedTheme && themes.includes(savedTheme)) {
668 +		currentThemeIndex = themes.indexOf(savedTheme);
669 +		setTheme(savedTheme);
670 +	} else {
671 +		// Use default theme
672 +		setTheme(themes[0]);
673 +	}
674 +}
675 +
676 +// F2 key handler for theme cycling
677 +document.addEventListener('keydown', (e) => {
678 +	if (e.key === 'F2' && !isF2Pressed) {
679 +		e.preventDefault();
680 +		isF2Pressed = true;
681 +		cycleTheme();
682 +	}
683 +});
684 +
685 +document.addEventListener('keyup', (e) => {
686 +	if (e.key === 'F2') {
687 +		isF2Pressed = false;
688 +	}
689 +});
690 +
691 +// Load theme on page load
692 +loadThemeFromLocalStorage();
style.css
  1 +/* Named color palette and default theme */
  2  :root {
    -	--color-bg-body: #f5f5f0;
    -	--color-bg-page: #fafaf8;
    -	--color-grid-line: #d0d0d0;
    -	--color-accent: #79aeea;
    -	--color-white: white;
  3 +	/* Default colors */
  4 +	--white: white;
  5 +	--black: black;
  6 +	--default-grid: #d0d0d0;
  7  	--shadow: #00000044;
  8  
  9 +	/* guac theme colors */
 10 +	--onion: #a81d8f;
 11 +	--garlic: #fefef4;
 12 +	--lime: #a9cf51;
 13 +	--avocado: #7a8520;
 14 +
 15 +	/* toast theme colors */
 16 +	--butter: #ffe598;
 17 +	--cream: #fffef9;
 18 +	--crumb: #decca3;
 19 +	--peanut-butter: #be852a;
 20 +
 21 +	/* mojito theme colors */
 22 +	--mint: #9febaa;
 23 +	--soda: #f8fffe;
 24 +	--zest: #9ee3ad;
 25 +	--watermelon: #f8599b;
 26 +
 27 +	/* grape-soda theme colors */
 28 +	--natural-flavors: #d4c5f9;
 29 +	--effervescence: #f9f7ff;
 30 +	--concentrate: #b8a9dd;
 31 +	--syrup: #7c5cbe;
 32 +
 33 +	/* coral theme colors */
 34 +	--reef: #ff9b87;
 35 +	--pearl: #fff8f6;
 36 +	--sand: #f0bcb3;
 37 +	--anemone: #ff6b51;
 38 +
 39 +	/* sea-breeze theme colors */
 40 +	--cerulean: #a3d5ff;
 41 +	--mist: #f7fcff;
 42 +	--horizon: #85c0f5;
 43 +	--lagoon: #2196f3;
 44 +
 45  	/* Grid configuration */
 46  	--grid-cols: 50;
 47  	--grid-rows: 66;

 52  	/* Grid as percentage of page: 200/216 = 92.59%, 264/279.4 = 94.48% */
 53  	--grid-width-percent: 92.59%;
 54  	--grid-height-percent: 94.48%;
 55 +
 56 +	/* Default theme (sea-breeze) */
 57 +	--desk: var(--cerulean);
 58 +	--page: var(--mist);
 59 +	--grid-line: var(--horizon);
 60 +	--accent: var(--lagoon);
 61 +
 62 +	/* Set text color for all themes */
 63 +	color: var(--white);
 64 +}
 65 +
 66 +/* Theme definitions */
 67 +:root[data-theme="guac"] {
 68 +	--desk: var(--avocado);
 69 +	--page: var(--garlic);
 70 +	--grid-line: var(--lime);
 71 +	--accent: var(--onion);
 72 +}
 73 +
 74 +:root[data-theme="toast"] {
 75 +	--desk: var(--butter);
 76 +	--page: var(--cream);
 77 +	--grid-line: var(--crumb);
 78 +	--accent: var(--peanut-butter);
 79 +}
 80 +
 81 +:root[data-theme="mojito"] {
 82 +	--desk: var(--mint);
 83 +	--page: var(--soda);
 84 +	--grid-line: var(--zest);
 85 +	--accent: var(--watermelon);
 86 +}
 87 +
 88 +:root[data-theme="grape-soda"] {
 89 +	--desk: var(--natural-flavors);
 90 +	--page: var(--effervescence);
 91 +	--grid-line: var(--concentrate);
 92 +	--accent: var(--syrup);
 93 +}
 94 +
 95 +:root[data-theme="coral"] {
 96 +	--desk: var(--reef);
 97 +	--page: var(--pearl);
 98 +	--grid-line: var(--sand);
 99 +	--accent: var(--anemone);
100 +}
101 +
102 +:root[data-theme="sea-breeze"] {
103 +	--desk: var(--cerulean);
104 +	--page: var(--mist);
105 +	--grid-line: var(--horizon);
106 +	--accent: var(--lagoon);
107  }
108  
109  * {

113  }
114  
115  body {
    -	background: var(--color-bg-body);
116 +	background: var(--desk);
117  	display: flex;
118  	justify-content: center;
119  	align-items: flex-start;

128  	width: 100%;
129  	/* Maintain 8.5:11 aspect ratio (11/8.5 = 1.294117647) */
130  	aspect-ratio: 8.5 / 11;
    -	background: var(--color-bg-page);
131 +	background: var(--page);
132  	position: relative;
    -	box-shadow: 0 2px 10px rgba(0,0,0,0.1);
133 +	box-shadow: 0 8px 24px rgba(0,0,0,0.15), 0 2px 8px rgba(0,0,0,0.08);
134  	display: flex;
135  	align-items: center;
136  	justify-content: center;

153  	width: 100%;
154  	height: 100%;
155  	box-sizing: border-box;
    -	border-right: 1px dashed var(--color-grid-line);
    -	border-bottom: 1px dashed var(--color-grid-line);
156 +	border-right: 1px dashed var(--grid-line);
157 +	border-bottom: 1px dashed var(--grid-line);
158  	margin: 0;
159  	padding: 0;
160  	display: block;
161  }
162  
163  .grid-cell:nth-child(-n+50) {
    -	border-top: 1px dashed var(--color-grid-line);
164 +	border-top: 1px dashed var(--grid-line);
165  }
166  
167  .grid-cell:nth-child(50n+1) {
    -	border-left: 1px dashed var(--color-grid-line);
168 +	border-left: 1px dashed var(--grid-line);
169  }
170  
171  .image-container {

174  	outline: 2px solid transparent;
175  	outline-offset: -2px;
176  	transition: outline-color 0.2s;
    -	background: var(--color-bg-page);
177 +	background: var(--page);
178  }
179  
180  .image-container::after {

201  }
202  
203  .image-container:hover::after {
    -	border-color: var(--color-accent);
204 +	border-color: var(--accent);
205  }
206  
207  .image-container.dragging {

209  }
210  
211  .image-container.dragging::after {
    -	border-color: var(--color-accent);
212 +	border-color: var(--accent);
213  }
214  
215  .image-container.resizing::after {
    -	border-color: var(--color-accent);
216 +	border-color: var(--accent);
217  }
218  
219  body.resizing *,

233  
234  .resize-handle {
235  	position: absolute;
    -	background: var(--color-accent);
236 +	background: var(--accent);
237  	opacity: 0;
238  	transition: opacity 0.2s;
239  	z-index: 2;

245  
246  .dimension-label {
247  	position: absolute;
    -	background: var(--color-accent);
    -	color: var(--color-white);
248 +	background: var(--accent);
249 +	color: var(--text);
250  	padding: 4px 10px;
251  	border-radius: 12px;
252  	font-size: 16px;
253  	font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
254  	font-weight: 500;
    -	text-shadow: 1px 1px 1px var(--shadow);
255  	line-height: 1;
256  	opacity: 0;
257  	transition: opacity 0.2s;

289  	position: absolute;
290  	width: 10px;
291  	height: 10px;
    -	background: var(--color-accent);
292 +	background: var(--accent);
293  	border-radius: 50%;
294  }
295  

321  	}
322  
323  	body {
    -		background: var(--color-white);
324 +		background: var(--white);
325  		padding: 0;
326  		margin: 0;
327  		display: block;

336  		box-shadow: none;
337  		margin: 0;
338  		padding: 0;
    -		background: var(--color-white);
339 +		background: var(--white);
340  		display: flex !important;
341  		align-items: center !important;
342  		justify-content: center !important;

353  	}
354  
355  	.grid-cell {
    -		border-right: 1px dashed var(--color-grid-line) !important;
    -		border-bottom: 1px dashed var(--color-grid-line) !important;
356 +		border-right: 1px dashed var(--default-grid) !important;
357 +		border-bottom: 1px dashed var(--default-grid) !important;
358  		box-sizing: border-box !important;
359  		width: 100% !important;
360  		height: 100% !important;

363  	}
364  
365  	.grid-cell:nth-child(-n+50) {
    -		border-top: 1px dashed var(--color-grid-line) !important;
366 +		border-top: 1px dashed var(--default-grid) !important;
367  	}
368  
369  	.grid-cell:nth-child(50n+1) {
    -		border-left: 1px dashed var(--color-grid-line) !important;
370 +		border-left: 1px dashed var(--default-grid) !important;
371  	}
372  
373  	.image-container {
374  		outline: none !important;
375  		position: absolute !important;
    -		background: var(--color-white) !important;
376 +		background: var(--white) !important;
377  		/* Recalculate positions using cell coordinates and print cell size */
378  		left: calc(var(--x-cell) * var(--cell-size-mm)) !important;
379  		top: calc(var(--y-cell) * var(--cell-size-mm)) !important;