diff --git a/assets/css/display.css b/assets/css/display.css
new file mode 100644
index 0000000..9f78b52
--- /dev/null
+++ b/assets/css/display.css
@@ -0,0 +1,80 @@
+/* Studio 2 display skins — Classic | Studio. Layout tokens only; palette matches main.css. */
+
+:root,
+[data-display='classic'],
+[data-display='studio'] {
+ --display-bg: #09090b;
+ --display-fg: #e4e4e7;
+ --display-muted: #a1a1aa;
+ --display-border: rgba(255, 255, 255, 0.1);
+ --display-amber: #fbbf24;
+ --display-amber-fg: #09090b;
+ --display-radius: 8px;
+ --display-focus: rgba(252, 211, 77, 0.5);
+}
+
+[data-display='classic'] {
+ --chrome-gap: 0.5rem;
+ --rail-h: 2.25rem;
+ --card-pad: 0.75rem;
+ --display-type-title: 1.75rem;
+ --display-type-body: 0.875rem;
+}
+
+[data-display='studio'] {
+ --chrome-gap: 1rem;
+ --rail-h: 3rem;
+ --card-pad: 1rem;
+ --display-type-title: 1.875rem;
+ --display-type-body: 0.9375rem;
+}
+
+[data-display] .display-rail {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ min-height: var(--rail-h);
+}
+
+[data-display] .display-rail__seg {
+ min-height: var(--rail-h);
+ border-radius: var(--display-radius);
+ border: 1px solid var(--display-border);
+ padding: 0.5rem 1rem;
+ font-size: var(--display-type-body);
+ outline: none;
+ transition: border-color 0.15s ease, background-color 0.15s ease, color 0.15s ease;
+}
+
+[data-display] .display-rail__seg:focus-visible {
+ box-shadow: 0 0 0 2px var(--display-focus);
+}
+
+[data-display] .display-rail__seg[aria-pressed='true'],
+[data-display] .display-rail__seg[data-active='true'] {
+ border-color: var(--display-amber);
+ background: var(--display-amber);
+ color: var(--display-amber-fg);
+}
+
+[data-display] .display-rail__seg:disabled,
+[data-display] .display-rail__seg[aria-disabled='true'] {
+ opacity: 0.55;
+ cursor: not-allowed;
+}
+
+[data-display='studio'] .display-rail__seg {
+ font-weight: 600;
+ padding: 0.625rem 1.25rem;
+}
+
+[data-display] .display-cta {
+ border-radius: calc(var(--display-radius) + 4px);
+ background: var(--display-amber);
+ color: var(--display-amber-fg);
+ font-weight: 600;
+}
+
+[data-display] .display-cta:focus-visible {
+ box-shadow: 0 0 0 2px var(--display-focus);
+}
diff --git a/components/SettingsForm.vue b/components/SettingsForm.vue
index 643b195..1ef255e 100644
--- a/components/SettingsForm.vue
+++ b/components/SettingsForm.vue
@@ -20,6 +20,37 @@
:class="noticeError ? 'border-red-400/30 bg-red-950/40 text-red-100' : 'border-emerald-400/20 bg-emerald-950/30 text-emerald-100'"
>{{ notice }}
+
+ Display
+ Layout only. Jobs, engines, and library IDs stay the same.
+
+
+
+
+
+
GPU & defaults
@@ -422,11 +453,12 @@ export interface SettingsFolder {
unlocked: boolean
}
-type SettingsPanel = 'gpu' | 'storage' | 'login' | 'folders' | 'loras' | 'keeps' | 'prompts'
+type SettingsPanel = 'display' | 'gpu' | 'storage' | 'login' | 'folders' | 'loras' | 'keeps' | 'prompts'
type LoraKindFilter = 'all' | 'image' | 'video'
const route = useRoute()
const router = useRouter()
+const { storedDisplayMode, setDisplayMode } = useDisplayMode()
const folders = ref
([])
const authMode = ref('')
@@ -435,7 +467,7 @@ const notice = ref('')
const noticeError = ref(false)
let noticeTimer: ReturnType | null = null
-const panelIds: SettingsPanel[] = ['gpu', 'storage', 'login', 'folders', 'loras', 'keeps', 'prompts']
+const panelIds: SettingsPanel[] = ['display', 'gpu', 'storage', 'login', 'folders', 'loras', 'keeps', 'prompts']
function parsePanel(raw: unknown): SettingsPanel {
const value = String(raw || '')
@@ -456,6 +488,7 @@ const activePanel = computed({
const navItems = computed(() => {
const items: Array<{ id: SettingsPanel; label: string }> = [
+ { id: 'display', label: 'Display' },
{ id: 'gpu', label: 'GPU & defaults' },
{ id: 'storage', label: 'Storage' }
]
diff --git a/components/studio2/Bench.vue b/components/studio2/Bench.vue
index ed6bd6c..e3fed91 100644
--- a/components/studio2/Bench.vue
+++ b/components/studio2/Bench.vue
@@ -1,5 +1,5 @@
-
+
Studio 2
@@ -255,6 +255,7 @@ import {randomSeed} from '~/shared/studio2/seed.mjs'
import {DEFAULT_MUSIC_LYRICS,DEFAULT_MUSIC_TAGS} from '~/utils/music'
const xaigen=useRuntimeConfig().public.studio==='xaigen'
+const {displayMode}=useDisplayMode()
const picker=ref('')
const uploading=ref(false),uploadMessage=ref('')
const revealed=ref>(new Set())
diff --git a/composables/useDisplayMode.ts b/composables/useDisplayMode.ts
new file mode 100644
index 0000000..8f8dec6
--- /dev/null
+++ b/composables/useDisplayMode.ts
@@ -0,0 +1,82 @@
+export type DisplayMode = 'classic' | 'studio'
+
+const STORAGE_KEY = 'aigen.displayMode'
+
+function parseMode(raw: unknown): DisplayMode | null {
+ const value = String(raw || '').trim().toLowerCase()
+ return value === 'classic' || value === 'studio' ? value : null
+}
+
+function readStored(): DisplayMode {
+ if (!import.meta.client) return 'studio'
+ try {
+ return parseMode(localStorage.getItem(STORAGE_KEY)) || 'studio'
+ } catch {
+ return 'studio'
+ }
+}
+
+function writeStored(mode: DisplayMode) {
+ if (!import.meta.client) return
+ try {
+ localStorage.setItem(STORAGE_KEY, mode)
+ } catch { /* ignore quota / private mode */ }
+}
+
+/** Classic | Studio chrome skin. One Bench tree; layout only. */
+export function useDisplayMode() {
+ const route = useRoute()
+ const displayMode = useState('aigen.displayMode', () => 'studio')
+ const sessionOverride = useState('aigen.displayMode.session', () => null)
+ const hydrated = useState('aigen.displayMode.hydrated', () => false)
+
+ const resolved = computed(() => {
+ const fromQuery = parseMode(route.query.display)
+ if (fromQuery) return fromQuery
+ if (sessionOverride.value) return sessionOverride.value
+ return displayMode.value
+ })
+
+ function setDisplayMode(mode: DisplayMode, opts?: { persist?: boolean; session?: boolean }) {
+ const persist = opts?.persist !== false
+ if (opts?.session) sessionOverride.value = mode
+ else sessionOverride.value = null
+ displayMode.value = mode
+ if (persist) writeStored(mode)
+ }
+
+ function hydrate() {
+ if (hydrated.value) return
+ hydrated.value = true
+ const fromQuery = parseMode(route.query.display)
+ if (fromQuery) {
+ sessionOverride.value = fromQuery
+ return
+ }
+ displayMode.value = readStored()
+ }
+
+ if (import.meta.client) {
+ onMounted(hydrate)
+ watch(
+ () => route.query.display,
+ (raw) => {
+ const fromQuery = parseMode(raw)
+ if (fromQuery) sessionOverride.value = fromQuery
+ else if (sessionOverride.value && !parseMode(route.query.display)) {
+ // Drop session override when query cleared; keep stored preference.
+ sessionOverride.value = null
+ displayMode.value = readStored()
+ }
+ }
+ )
+ }
+
+ return {
+ displayMode: resolved,
+ storedDisplayMode: displayMode,
+ setDisplayMode,
+ isStudio: computed(() => resolved.value === 'studio'),
+ isClassic: computed(() => resolved.value === 'classic')
+ }
+}
diff --git a/nuxt.config.ts b/nuxt.config.ts
index 709e39f..ba40732 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -4,7 +4,7 @@ const xaigenStudio = /^xaigen$/i.test(String(process.env.NUXT_PUBLIC_STUDIO || '
export default defineNuxtConfig({
compatibilityDate: '2025-08-01',
modules: ['@nuxtjs/tailwindcss'],
- css: ['~/assets/css/main.css'],
+ css: ['~/assets/css/main.css', '~/assets/css/display.css'],
app: {
head: {
title: 'AIGen — MiniMax H3',