diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 7119e8a..bb4aa48 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,7 +7,6 @@ import { TopBar } from './components/layout/TopBar' import { ScanProgress } from './components/ScanProgress' import { ToastContainer } from './components/ToastContainer' import { KeyboardHints } from './components/KeyboardHints' -import { AppFooter } from './components/AppFooter' import { PreviewView } from './components/preview/PreviewView' import { FilterBar } from './components/filter/FilterBar' import { DiscardActionBar } from './components/discard/DiscardActionBar' @@ -84,7 +83,6 @@ function App() { * glassy. Mounted here so it's centered against the timeline, * not the viewport (which would be offset by the sidebars). */} - {/* Right Sidebar */} diff --git a/frontend/src/assets/card.png b/frontend/src/assets/card.png new file mode 100644 index 0000000..5a50555 Binary files /dev/null and b/frontend/src/assets/card.png differ diff --git a/frontend/src/assets/desert.png b/frontend/src/assets/desert.png new file mode 100644 index 0000000..274f3bd Binary files /dev/null and b/frontend/src/assets/desert.png differ diff --git a/frontend/src/assets/mule-sprites.png b/frontend/src/assets/mule-sprites.png new file mode 100644 index 0000000..b3f203a Binary files /dev/null and b/frontend/src/assets/mule-sprites.png differ diff --git a/frontend/src/components/AppFooter.tsx b/frontend/src/components/AppFooter.tsx deleted file mode 100644 index a13204a..0000000 --- a/frontend/src/components/AppFooter.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Tiny corner footer pinned to the bottom-right of the main column. - * Renders "Built with hubris • ", refreshed - * once per page load. - */ - -const ROMAN_PAIRS: [number, string][] = [ - [1000, 'M'], - [900, 'CM'], - [500, 'D'], - [400, 'CD'], - [100, 'C'], - [90, 'XC'], - [50, 'L'], - [40, 'XL'], - [10, 'X'], - [9, 'IX'], - [5, 'V'], - [4, 'IV'], - [1, 'I'], -] - -function toRoman(n: number): string { - let result = '' - for (const [value, symbol] of ROMAN_PAIRS) { - while (n >= value) { - result += symbol - n -= value - } - } - return result -} - -export function AppFooter() { - const year = new Date().getFullYear() - return ( -
- - Built with hubris • {toRoman(year)} - -
- ) -} diff --git a/frontend/src/components/heaps/ActiveHeapCard.tsx b/frontend/src/components/heaps/ActiveHeapCard.tsx index d47d2f8..23d2cb0 100644 --- a/frontend/src/components/heaps/ActiveHeapCard.tsx +++ b/frontend/src/components/heaps/ActiveHeapCard.tsx @@ -4,6 +4,7 @@ import { ShoppingBasket } from 'lucide-react' import { useActiveHeapMembers } from '../../hooks/useActiveHeapMembersQuery' import { useFilterStore } from '../../store/filterStore' import { photos as photosApi, heaps as heapsApi } from '../../services/api' +import cardBg from '../../assets/card.png' /** How many thumbnails fan out across the stack at once. The newest is * drawn last (top), older ones fan back-left and back-right. */ @@ -75,10 +76,19 @@ export function ActiveHeapCard() { {/* Stack row. Re-keyed on activeHeap.id so switching heaps tears * the animation context down cleanly instead of trying to - * crossfade unrelated photos. */} + * crossfade unrelated photos. The desert scene sits behind the + * fanned thumbnails — `cover` + `bottom` keeps the dunes anchored + * so the cacti frame the photos rather than the (transparent) sky. */}
{visible.length === 0 ? (
diff --git a/frontend/src/components/layout/TopBar.tsx b/frontend/src/components/layout/TopBar.tsx index 1647e88..cfd64fc 100644 --- a/frontend/src/components/layout/TopBar.tsx +++ b/frontend/src/components/layout/TopBar.tsx @@ -1,5 +1,6 @@ import { PanelLeftOpen, PanelRightOpen } from 'lucide-react' -import muliLogo from '../../assets/muli-logo.png' +import desertBg from '../../assets/desert.png' +import muleSprites from '../../assets/mule-sprites.png' interface TopBarProps { leftSidebarOpen: boolean @@ -8,10 +9,34 @@ interface TopBarProps { onExpandRight: () => void } +// Block-character ASCII rendering of "Mulimago" — sits on a black plate +// in place of the old text title. +const MULIMAGO_ASCII = `▖ ▖ ▜ ▘ +▛▖▞▌▌▌▐ ▌▛▛▌▀▌▛▌█▌ +▌▝ ▌▙▌▐▖▌▌▌▌█▌▙▌▙▖` + +const ROMAN_PAIRS: [number, string][] = [ + [1000, 'M'], [900, 'CM'], [500, 'D'], [400, 'CD'], + [100, 'C'], [90, 'XC'], [50, 'L'], [40, 'XL'], + [10, 'X'], [9, 'IX'], [5, 'V'], [4, 'IV'], [1, 'I'], +] + +function toRoman(n: number): string { + let result = '' + for (const [value, symbol] of ROMAN_PAIRS) { + while (n >= value) { + result += symbol + n -= value + } + } + return result +} + /** - * Slim top bar — logo on the left, settings gear on the right. The - * active heap badge moved into the Heaps panel in the left sidebar - * (where it actually relates to the heap rows the user navigates to). + * Slim top bar — animated walking mule on the left over a tiled desert + * backdrop, settings gear on the right. The active heap badge moved into + * the Heaps panel in the left sidebar (where it actually relates to the + * heap rows the user navigates to). * * Also hosts the "expand sidebar" affordances: when a side panel is * collapsed, a small panel-open icon appears on the corresponding edge @@ -26,32 +51,68 @@ export function TopBar({ onExpandRight, }: TopBarProps) { return ( -
-
+
+
{!leftSidebarOpen && ( )} - Mulimago -

Mulimago

+
+
+          {MULIMAGO_ASCII}
+        
-
+
{!rightSidebarOpen && ( )} + + Built with hubris • {toRoman(new Date().getFullYear())} +
) diff --git a/frontend/src/index.css b/frontend/src/index.css index 5ec91c1..7e4df1e 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -4,18 +4,18 @@ @layer base { :root { - --color-bg: #111110; - --color-surface: #161615; - --color-surface-2: #1c1c1a; - --color-surface-offset: #222220; - --color-border: rgba(255, 255, 255, 0.08); - --color-text: #e8e6e0; - --color-text-muted: #878580; - --color-text-faint: #4a4845; - --color-primary: #3b82f6; - --color-pick: #4f9e5c; - --color-reject: #c25a5a; - --color-star: #d4a340; + --color-bg: #1a1424; + --color-surface: #221c2f; + --color-surface-2: #2a2339; + --color-surface-offset: #352c46; + --color-border: rgba(240, 193, 136, 0.10); + --color-text: #f0e6d2; + --color-text-muted: #a8997d; + --color-text-faint: #5e5448; + --color-primary: #e8965a; + --color-pick: #8aaa5a; + --color-reject: #d06464; + --color-star: #f0c188; } body { @@ -40,4 +40,25 @@ ::-webkit-scrollbar-thumb:hover { @apply bg-text-faint; +} + +/* Mule walk cycle — 3x2 sprite sheet (6 frames). Each keyframe holds + for one step thanks to `steps(1)` so the background snaps frame-to-frame + instead of interpolating. */ +@keyframes mule-walk { + 0% { background-position: 0% 0%; } + 16.66% { background-position: 50% 0%; } + 33.33% { background-position: 100% 0%; } + 50% { background-position: 0% 100%; } + 66.66% { background-position: 50% 100%; } + 83.33% { background-position: 100% 100%; } + 100% { background-position: 0% 0%; } +} + +/* Parallax desert scroll — moves the desert layer right→left by exactly + one tile width so the loop is seamless. The tile width is fixed in + CSS (see TopBar.tsx) so the keyframe end value matches. */ +@keyframes desert-scroll { + from { background-position-x: 0px, 0px; } + to { background-position-x: -200px, 0px; } } \ No newline at end of file diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 19571cc..b54878c 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -8,19 +8,22 @@ export default { theme: { extend: { colors: { - // Dark-first color scheme for photo apps - bg: '#111110', - surface: '#161615', - 'surface-2': '#1c1c1a', - 'surface-offset': '#222220', - border: 'rgba(255,255,255,0.08)', - text: '#e8e6e0', - 'text-muted': '#878580', - 'text-faint': '#4a4845', - primary: '#3b82f6', // saturated blue (Tailwind blue-500) — high contrast on dark bg AND on photo content - pick: '#4f9e5c', // green for picked - reject: '#c25a5a', // red for rejected - star: '#d4a340', // amber for stars + // Desert dusk palette — warm, slightly indigo darks pulled from + // the TopBar sunset gradient. Dark enough that photo content + // stays the visual focus, but no longer the cool neutral greys + // we started with. + bg: '#1a1424', // deep dusk indigo (was near-black) + surface: '#221c2f', // panel base + 'surface-2': '#2a2339', // raised panels / hover + 'surface-offset': '#352c46', // chips, inputs + border: 'rgba(240, 193, 136, 0.10)', // warm sand tint, low alpha + text: '#f0e6d2', // warm cream + 'text-muted': '#a8997d', // sandy beige + 'text-faint': '#5e5448', // dark sand + primary: '#e8965a', // sunset orange — pulled from horizon + pick: '#8aaa5a', // sage / cactus green + reject: '#d06464', // warmer red + star: '#f0c188', // pale amber matching horizon }, fontFamily: { sans: ['Geist', 'system-ui', 'sans-serif'],