diff --git a/branding/README.md b/branding/README.md index b84ba6c..00aac49 100644 --- a/branding/README.md +++ b/branding/README.md @@ -47,12 +47,16 @@ When both `logo.svg` and `logo.png` are present, SVG wins. "name": "AIO", // required — drives PWA manifest name "shortName": "AIO", // optional — PWA home-screen label; defaults to `name` "themeColor": "#1f2937", // optional — PWA chrome color override (otherwise each standalone keeps its accent) - "backgroundColor": "#fff" // optional — PWA splash background + "backgroundColor": "#fff", // optional — PWA splash background + "theme": "light", // optional — default in-app mode: light | dark | system + "palette": "darkmatter" // optional — default in-app palette (see PALETTES) } ``` `themeColor` and `backgroundColor` are *overrides*, not defaults. When unset, each standalone's own accent applies (wallet yellow `#eab308`, chat green `#16a34a`, …) — so the default brand kit preserves the per-app visual identity, and a deployer who wants unified chrome adds the override. +`theme` and `palette` set the **in-app** color scheme — distinct from `themeColor` (which is only the PWA chrome / status-bar color). They define the *initial* default a fresh visitor sees; once a user picks a theme in-app it's stored in `localStorage` and always wins. `palette` must be one of the names in `src/components/theme-provider` (`PALETTES`): `catppuccin` (the built-in default), `countrysidecastle`, `darkmatter`, `emeraldforest`, `lightgreen`, `neobrut`, `starrynight`. Each palette has both a light and a dark variant, so e.g. "darkmatter light" is `{ "theme": "light", "palette": "darkmatter" }`. Unset → the app's built-ins (`dark` + `catppuccin`). Applies app-wide (hub + every standalone). + ## Per-standalone overrides Place a logo at `branding//icons//logo.{svg,png}` to override the brand's primary logo for a single standalone build. @@ -67,6 +71,37 @@ Resolution at build time: `` is set via `BRAND_APP` env var (the standalone build script sets this; deployers don't touch it directly). +## Optional banner (logo + wordmark lockup) + +A brand may ship a **banner** — a single wide image combining the logo and the wordmark — that replaces the logo + app-name pair in a standalone's header (currently the events page header). Banners are optional: brands without one keep the default logo + name rendering. + +``` +branding// + banner.svg # preferred — crisp at any size, recolorable + banner.png # fallback (wide, transparent background) + icons/ + events/banner.svg # optional per-standalone override +``` + +Resolution mirrors the logo chain (`resolveAppBanner` in `vite-branding.ts`): + +1. `branding//icons//banner.svg` +2. `branding//icons//banner.png` +3. `branding//banner.svg` +4. `branding//banner.png` +5. No banner → header falls back to logo + name (no error). + +SVG is strongly preferred — a banner is wide and rasterizes poorly when scaled. Components reference it via the build-time `@brand-app-banner` alias; whether it renders is driven by the `VITE_APP_BANNER` flag, so the component stays brand-agnostic. + +> **⚠️ Outline text to paths in any SVG you ship.** The browser only has +> web-safe fonts — if a banner/logo SVG keeps live `` elements that +> reference a designer font (e.g. a decorative display face), the browser +> substitutes a default font and the glyphs render wrong (we hit this with +> a mangled `!` in the "Oyez!" banner). In Inkscape: **Edit → Select All in +> All Layers (Ctrl+Alt+A)** — plain Select All only covers the current +> layer — then **Path → Object to Path (Shift+Ctrl+C)**, and save. Verify +> with `grep -c ' { - const theme = ref('dark') + const theme = ref(DEFAULT_THEME) const systemTheme = ref<'dark' | 'light'>('light') const palette = ref(DEFAULT_PALETTE) @@ -45,7 +63,7 @@ const useTheme = () => { } const applyPalette = () => { - if (palette.value === DEFAULT_PALETTE) { + if (palette.value === BASE_PALETTE) { document.documentElement.removeAttribute('data-theme') } else { document.documentElement.setAttribute('data-theme', palette.value) diff --git a/src/modules/events/views/EventsPage.vue b/src/modules/events/views/EventsPage.vue index 39af9bd..10447b5 100644 --- a/src/modules/events/views/EventsPage.vue +++ b/src/modules/events/views/EventsPage.vue @@ -10,11 +10,17 @@ import { } from '@/components/ui/collapsible' import { SlidersHorizontal, CalendarDays, Plus } from 'lucide-vue-next' import brandAppLogoUrl from '@brand-app-logo?url' +import brandAppBannerUrl from '@brand-app-banner?url' // Brand name flows through VITE_APP_NAME (set in vite.events.config.ts // from brand.name). cfaun → "Oyez!", default → "Events", etc. Falls // back to the i18n string only when no brand is configured (shouldn't // happen in production builds, but defensive). const appName: string = (import.meta.env.VITE_APP_NAME as string) || '' +// When the active brand ships a banner (wide logo+wordmark lockup), it +// replaces the logo + name pair in the header. The flag is set at build +// time; brandAppBannerUrl falls back to the logo when unset, so we only +// render the banner when the flag is truthy. +const hasBanner = (import.meta.env.VITE_APP_BANNER as string) === '1' import { useEvents } from '../composables/useEvents' import { useEventsStore } from '../stores/events' import EventSearchOverlay from '../components/EventSearchOverlay.vue' @@ -81,17 +87,27 @@ function openCalendar() {