diff --git a/bicorder-app/public/favicon.svg b/bicorder-app/public/favicon.svg
new file mode 100644
index 0000000..8b5fd82
--- /dev/null
+++ b/bicorder-app/public/favicon.svg
@@ -0,0 +1,14 @@
+
diff --git a/bicorder-app/public/icon-192.png b/bicorder-app/public/icon-192.png
new file mode 100644
index 0000000..e451b6d
Binary files /dev/null and b/bicorder-app/public/icon-192.png differ
diff --git a/bicorder-app/public/icon-512.png b/bicorder-app/public/icon-512.png
new file mode 100644
index 0000000..96e1ecd
Binary files /dev/null and b/bicorder-app/public/icon-512.png differ
diff --git a/bicorder-app/src/App.svelte b/bicorder-app/src/App.svelte
index c05934a..8daa13b 100644
--- a/bicorder-app/src/App.svelte
+++ b/bicorder-app/src/App.svelte
@@ -14,6 +14,86 @@
data.metadata.timestamp = new Date().toISOString();
}
+ // View mode and navigation state
+ type ViewMode = 'focused' | 'list';
+ let viewMode: ViewMode = 'focused'; // Focused is default
+ let currentScreen = 0;
+ let refreshKey = 0; // Used to force component refresh in focused mode
+
+ // Screen types
+ type Screen =
+ | { type: 'metadata' }
+ | { type: 'gradient'; setIndex: number; gradientIndex: number; gradient: Gradient; setName: string }
+ | { type: 'analysis'; index: number; gradient: AnalysisGradient }
+ | { type: 'export' };
+
+ // Calculate all screens based on current shortform setting
+ function calculateScreens(): Screen[] {
+ const screens: Screen[] = [];
+
+ // Metadata screen
+ screens.push({ type: 'metadata' });
+
+ // Diagnostic gradient screens
+ data.diagnostic.forEach((diagnosticSet, setIndex) => {
+ diagnosticSet.gradients.forEach((gradient, gradientIndex) => {
+ if (!data.metadata.shortform || gradient.shortform) {
+ screens.push({
+ type: 'gradient',
+ setIndex,
+ gradientIndex,
+ gradient,
+ setName: diagnosticSet.set_name
+ });
+ }
+ });
+ });
+
+ // Analysis screens (not in shortform)
+ if (!data.metadata.shortform) {
+ data.analysis.forEach((gradient, index) => {
+ screens.push({ type: 'analysis', index, gradient });
+ });
+ }
+
+ // Export screen
+ screens.push({ type: 'export' });
+
+ return screens;
+ }
+
+ $: screens = calculateScreens();
+ $: currentScreenData = screens[currentScreen];
+ $: totalScreens = screens.length;
+
+ function goToNextScreen() {
+ if (currentScreen < totalScreens - 1) {
+ currentScreen++;
+ }
+ }
+
+ function goToPrevScreen() {
+ if (currentScreen > 0) {
+ currentScreen--;
+ }
+ }
+
+ function toggleViewMode() {
+ viewMode = viewMode === 'focused' ? 'list' : 'focused';
+ }
+
+ // Generate ASCII progress bar
+ function generateProgressBar(current: number, total: number): string {
+ const filled = '#';
+ const empty = '-';
+ const barLength = Math.min(total, 20); // Cap at 20 characters for display
+ const filledCount = Math.round((current / total) * barLength);
+ const emptyCount = barLength - filledCount;
+ return filled.repeat(filledCount) + empty.repeat(emptyCount);
+ }
+
+ $: progressBar = generateProgressBar(currentScreen + 1, totalScreens);
+
// Load saved state from localStorage
onMount(() => {
const saved = localStorage.getItem('bicorder-state');
@@ -45,6 +125,27 @@
console.error('Failed to load saved state:', e);
}
}
+
+ // Keyboard navigation for focused mode
+ function handleKeyDown(e: KeyboardEvent) {
+ if (viewMode !== 'focused') return;
+
+ // Only navigate if not typing in an input
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
+ return;
+ }
+
+ if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
+ e.preventDefault();
+ goToNextScreen();
+ } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
+ e.preventDefault();
+ goToPrevScreen();
+ }
+ }
+
+ window.addEventListener('keydown', handleKeyDown);
+ return () => window.removeEventListener('keydown', handleKeyDown);
});
// Save state whenever it changes
@@ -117,57 +218,181 @@
-
+ {#if viewMode === 'list'}
+
+
- {#each data.diagnostic as diagnosticSet, setIndex}
-
-
- {diagnosticSet.set_description}
+ {#each data.diagnostic as diagnosticSet, setIndex}
+
+
+ {diagnosticSet.set_description}
- {#each diagnosticSet.gradients as gradient, gradientIndex}
- {#if !data.metadata.shortform || gradient.shortform}
- {
- data.diagnostic[setIndex].gradients[gradientIndex].value = e.detail ?? null;
+ {#each diagnosticSet.gradients as gradient, gradientIndex}
+ {#if !data.metadata.shortform || gradient.shortform}
+ {
+ data.diagnostic[setIndex].gradients[gradientIndex].value = e.detail ?? null;
+ data = data;
+ }}
+ on:notes={(e) => {
+ data.diagnostic[setIndex].gradients[gradientIndex].notes = e.detail;
+ data = data;
+ }}
+ />
+ {/if}
+ {/each}
+
+ {/each}
+
+
+
+
+ {#each data.analysis as analysisItem, index}
+ {
+ if (!analysisItem.automated) {
+ data.analysis[index].value = e.detail ?? null;
data = data;
- }}
- on:notes={(e) => {
- data.diagnostic[setIndex].gradients[gradientIndex].notes = e.detail;
- data = data;
- }}
- />
- {/if}
+ }
+ }}
+ on:notes={(e) => {
+ data.analysis[index].notes = e.detail;
+ data = data;
+ }}
+ />
{/each}
- {/each}
-
-
+
- {#each data.analysis as analysisItem, index}
- {
- if (!analysisItem.automated) {
- data.analysis[index].value = e.detail ?? null;
- data = data;
- }
- }}
- on:notes={(e) => {
- data.analysis[index].notes = e.detail;
- data = data;
- }}
- />
- {/each}
-
+ {:else}
+
+
+ {#if currentScreenData.type === 'metadata'}
+
+
+
-
+ {:else if currentScreenData.type === 'gradient'}
+ {@const screen = currentScreenData}
+
+
{screen.setName.toUpperCase()}
+
+
+
+
{screen.gradient.term_left}
+
{screen.gradient.term_left_description}
+
+
+ {#key refreshKey}
+
{
+ data.diagnostic[screen.setIndex].gradients[screen.gradientIndex].value = e.detail ?? null;
+ data = data;
+ refreshKey++; // Force component refresh
+ }}
+ on:notes={(e) => {
+ data.diagnostic[screen.setIndex].gradients[screen.gradientIndex].notes = e.detail;
+ data = data;
+ refreshKey++; // Force component refresh
+ }}
+ />
+ {/key}
+
+
+
{screen.gradient.term_right}
+
{screen.gradient.term_right_description}
+
+
+
+
+ {:else if currentScreenData.type === 'analysis'}
+ {@const screen = currentScreenData}
+
+
ANALYSIS
+
+
+
+
{screen.gradient.term_left}
+
{screen.gradient.term_left_description}
+
+
+ {#key refreshKey}
+
{
+ if (!screen.gradient.automated) {
+ data.analysis[screen.index].value = e.detail ?? null;
+ data = data;
+ refreshKey++; // Force component refresh
+ }
+ }}
+ on:notes={(e) => {
+ data.analysis[screen.index].notes = e.detail;
+ data = data;
+ refreshKey++; // Force component refresh
+ }}
+ />
+ {/key}
+
+
+
{screen.gradient.term_right}
+
{screen.gradient.term_right_description}
+
+
+
+
+ {:else if currentScreenData.type === 'export'}
+
+
+
+ {/if}
+
+
+
+
+
+
+
+
+
+
+
+
{progressBar}
+
{currentScreen + 1} / {totalScreens}
+
+
+ {/if}
diff --git a/bicorder-app/src/components/AnalysisDisplay.svelte b/bicorder-app/src/components/AnalysisDisplay.svelte
index 716ba78..d40e1f2 100644
--- a/bicorder-app/src/components/AnalysisDisplay.svelte
+++ b/bicorder-app/src/components/AnalysisDisplay.svelte
@@ -4,6 +4,7 @@
import Tooltip from './Tooltip.svelte';
export let gradient: AnalysisGradient;
+ export let focusedMode: boolean = false;
const dispatch = createEventDispatcher<{
change: number | null;
@@ -98,12 +99,14 @@
-
-
-
- {gradient.term_left}
-
-
+
+ {#if !focusedMode}
+
+
+ {gradient.term_left}
+
+
+ {/if}
-
-
- {gradient.term_right}
-
-
+ {#if !focusedMode}
+
+
+ {gradient.term_right}
+
+
+ {/if}
@@ -200,6 +205,15 @@
margin-bottom: 0.5rem;
}
+ .gradient-row.focused {
+ display: flex;
+ justify-content: center;
+ }
+
+ .gradient-row.focused .bar-container {
+ width: 100%;
+ }
+
.term {
text-align: center;
font-size: 0.9rem;
@@ -257,6 +271,11 @@
font-size: 0.9rem;
}
+ .analysis-gradient .value-display {
+ margin-top: 0.5rem;
+ margin-bottom: 0.25rem;
+ }
+
.auto-label {
opacity: 0.6;
font-size: 0.8rem;
diff --git a/bicorder-app/src/components/ExportControls.svelte b/bicorder-app/src/components/ExportControls.svelte
index 427173c..b501a16 100644
--- a/bicorder-app/src/components/ExportControls.svelte
+++ b/bicorder-app/src/components/ExportControls.svelte
@@ -3,6 +3,7 @@
import type { BicorderState } from '../types';
export let data: BicorderState;
+ export let focusedMode: boolean = false;
const dispatch = createEventDispatcher<{
reset: void;
@@ -121,7 +122,7 @@
}
-