Files
numolus/src/components/SettingsPanel.vue
T
wkhandClaude Sonnet 5 56bb68ccd0 Show the theme selector as icons instead of text
The toolbar's theme-cycling button ("Theme: Auto") and the Settings
panel's Auto/Hell/Dunkel segmented control now use icons -- a sun, a
moon, and a half-filled circle for "follows system" -- drawn in this
project's existing hand-drawn SVG icon set (ToolbarIcon.vue) rather than
a new icon system or emoji. Tooltips/aria-labels keep the full text for
accessibility; the toolbar button's click-to-cycle behavior is
unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 00:17:14 +02:00

765 lines
28 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
/**
* Left inspector drawer, opened from the gear button next to the title.
* Gathers every important setting in one place: the selected object (table
* sizing / frozen rows & columns / position, or chart), the active sheet, and
* document-wide options (theme, CSV default, reset), plus the canvas zoom.
*/
import { computed } from 'vue';
import { useDocument } from '../store/document';
import { useTheme } from '../composables/useTheme';
import { NUMBER_LOCALES, NUM_FORMATS } from '../store/format';
import type { CellFormat, HAlign } from '../store/types';
import { DEFAULT_GRID_SIZE, MIN_GRID_SIZE, MAX_GRID_SIZE } from '../store/types';
import ToolbarIcon from './ToolbarIcon.vue';
const store = useDocument();
const { mode: themeMode } = useTheme();
const open = computed(() => store.ui.settingsOpen);
const obj = computed(() => store.activeObject.value);
const table = computed(() =>
obj.value?.kind === 'table' && obj.value.view ? obj.value : null,
);
const chart = computed(() => (obj.value?.kind === 'chart' ? obj.value : null));
const text = computed(() => (obj.value?.kind === 'text' ? obj.value : null));
const button = computed(() => (obj.value?.kind === 'button' ? obj.value : null));
const sheet = computed(() => store.activeSheet.value);
const num = (e: Event) => Number((e.target as HTMLInputElement).value);
const str = (e: Event) => (e.target as HTMLInputElement | HTMLSelectElement).value;
const chk = (e: Event) => (e.target as HTMLInputElement).checked;
const tableList = computed(() =>
store.doc.sheets.flatMap((s) => s.objects).filter((o) => o.kind === 'table'),
);
// ---- cell-selection formatting (only when a table cell range is selected) ---
const sel = computed(() => store.doc.selection);
const selFmt = computed<CellFormat>(() => store.selectionFormat.value);
const hasCellSel = computed(() => !!sel.value && sel.value.tableId === table.value?.id);
const selFocus = computed(() => sel.value?.focus ?? { col: 0, row: 0 });
const focusColW = computed(() =>
table.value ? (table.value.colWidths[selFocus.value.col] ?? 104) : 104,
);
const focusRowH = computed(() =>
table.value ? store.rowHeightOf(table.value.id, selFocus.value.row) : 26,
);
function applyFmt(patch: Partial<Record<keyof CellFormat, unknown>>) {
store.applySelectionFormat(patch); // applies to every rectangle of the selection
}
function setAlign(a: HAlign) {
applyFmt({ align: selFmt.value.align === a ? null : a });
}
function close() {
store.ui.settingsOpen = false;
}
function resetDoc() {
if (confirm('Vorbefülltes Dokument wiederherstellen? Alle Änderungen gehen verloren.')) {
store.resetToSeed();
}
}
</script>
<template>
<aside class="drawer" :class="{ open }" aria-label="Einstellungen">
<header>
<h2>Einstellungen</h2>
<button class="x" title="Schließen" @click="close">×</button>
</header>
<div class="body">
<!-- ===================== selected object ===================== -->
<section>
<h3>Objekt</h3>
<template v-if="table">
<label class="f">
<span>Name</span>
<input :value="table.name" @change="store.renameObject(table.id, str($event))" />
</label>
<div class="grid2">
<label class="f">
<span>Zeilen</span>
<input type="number" min="1" :value="table.rows" @change="store.setTableRows(table.id, num($event))" />
</label>
<label class="f">
<span>Spalten</span>
<input type="number" min="1" :value="table.cols" @change="store.setTableCols(table.id, num($event))" />
</label>
</div>
<label class="f">
<span>Darstellung</span>
<select :value="table.view.mode" @change="store.setTableView(table.id, { mode: str($event) === 'fixed' ? 'fixed' : 'auto' })">
<option value="auto">Automatisch – alle Zeilen zeigen</option>
<option value="fixed">Feste Größe – scrollbar</option>
</select>
</label>
<template v-if="table.view.mode === 'fixed'">
<div class="grid2">
<label class="f">
<span>Breite (px)</span>
<input type="number" min="200" step="10" :value="table.view.width" @change="store.setTableView(table.id, { width: num($event) })" />
</label>
<label class="f">
<span>Höhe (px)</span>
<input type="number" min="120" step="10" :value="table.view.height" @change="store.setTableView(table.id, { height: num($event) })" />
</label>
</div>
<div class="grid2">
<label class="f">
<span>Fixierte Kopfzeilen</span>
<input type="number" min="0" :max="table.rows - 1" :value="table.view.frozenRows" @change="store.setTableView(table.id, { frozenRows: num($event) })" />
</label>
<label class="f">
<span>Fixierte Spalten</span>
<input type="number" min="0" :max="table.cols - 1" :value="table.view.frozenCols" @change="store.setTableView(table.id, { frozenCols: num($event) })" />
</label>
</div>
</template>
<div class="grid2">
<label class="f">
<span>Position X</span>
<input type="number" min="0" step="4" :value="table.x" @change="store.moveObject(table.id, num($event), table.y)" />
</label>
<label class="f">
<span>Position Y</span>
<input type="number" min="0" step="4" :value="table.y" @change="store.moveObject(table.id, table.x, num($event))" />
</label>
</div>
<label class="f">
<span>Zahlenformat (Locale)</span>
<select :value="table.locale ?? ''" @change="store.setObjectLocale(table.id, str($event) || null)">
<option value="">— vom Blatt / Dokument</option>
<option v-for="l in NUMBER_LOCALES" :key="l.id" :value="l.id">{{ l.label }}</option>
</select>
</label>
<label class="f">
<span>Zeilenhöhe – Standard (px)</span>
<input type="number" min="14" max="240" :value="table.rowHeight ?? 26" @change="store.setTableRowHeight(table.id, num($event))" />
</label>
<label class="f">
<span>Tabellen-Hintergrund</span>
<span class="color">
<input type="color" :value="table.bg ?? '#ffffff'" @input="store.setObjectBg(table.id, str($event))" />
<button class="mini" title="Standard" @click="store.setObjectBg(table.id, null)">↺</button>
<em v-if="!table.bg">Design</em>
</span>
</label>
<label class="f">
<span>Tabellen-Textfarbe</span>
<span class="color">
<input type="color" :value="table.fg ?? '#1d1d1f'" @input="store.setObjectTextColor(table.id, str($event))" />
<button class="mini" title="Standard" @click="store.setObjectTextColor(table.id, null)">↺</button>
<em v-if="!table.fg">Design</em>
</span>
</label>
<label class="f row-inline">
<input
type="checkbox"
:checked="!!table.hideChromeUnfocused"
@change="store.setTableHideChromeUnfocused(table.id, chk($event))"
/>
<span>Ohne Rahmen/Kopfzeilen, wenn nicht ausgewählt</span>
</label>
<!-- ---- current cell selection ---- -->
<div v-if="hasCellSel" class="subsection">
<h4>
Auswahl · {{ store.activeCellName.value }}
<template v-if="store.selectionAggregate.value.cells > 1">
· {{ store.selectionAggregate.value.cells }} Zellen
</template>
</h4>
<div class="grid2">
<label class="f">
<span>Spaltenbreite (px)</span>
<input type="number" min="24" max="800" :value="focusColW" @change="store.setColWidth(table.id, selFocus.col, num($event))" />
</label>
<label class="f">
<span>Zeilenhöhe (px)</span>
<input type="number" min="14" max="240" :value="focusRowH" @change="store.setRowHeight(table.id, selFocus.row, num($event))" />
</label>
</div>
<label class="f">
<span>Ausrichtung</span>
<span class="seg">
<button :class="{ on: selFmt.align === 'left' }" @click="setAlign('left')">links</button>
<button :class="{ on: selFmt.align === 'center' }" @click="setAlign('center')">mitte</button>
<button :class="{ on: selFmt.align === 'right' }" @click="setAlign('right')">rechts</button>
</span>
</label>
<label class="f">
<span>Zahlenformat</span>
<select :value="selFmt.numFmt ?? 'auto'" @change="applyFmt({ numFmt: str($event) })">
<option v-for="nfmt in NUM_FORMATS" :key="nfmt.id" :value="nfmt.id">{{ nfmt.label }}</option>
</select>
</label>
<label class="f row-inline">
<button class="tgl" :class="{ on: selFmt.bold }" @click="applyFmt({ bold: !selFmt.bold })"><b>F</b></button>
<button class="tgl" :class="{ on: selFmt.italic }" @click="applyFmt({ italic: !selFmt.italic })"><i>K</i></button>
</label>
<label class="f">
<span>Zell-Textfarbe</span>
<span class="color">
<input type="color" :value="selFmt.color ?? '#1d1d1f'" @input="applyFmt({ color: str($event) })" />
<button class="mini" title="Standard" @click="applyFmt({ color: null })">↺</button>
</span>
</label>
<label class="f">
<span>Zell-Füllfarbe</span>
<span class="color">
<input type="color" :value="selFmt.bg ?? '#ffffff'" @input="applyFmt({ bg: str($event) })" />
<button class="mini" title="Standard" @click="applyFmt({ bg: null })">↺</button>
</span>
</label>
<button @click="store.clearSelectionFormat()">Formatierung entfernen</button>
</div>
<button class="danger" @click="store.deleteObject(table.id)">Tabelle löschen</button>
</template>
<template v-else-if="chart">
<label class="f">
<span>Name</span>
<input :value="chart.name" @change="store.renameObject(chart.id, str($event))" />
</label>
<label class="f">
<span>Ansicht</span>
<select :value="chart.view" @change="store.setChartView(chart.id, str($event) === 'line' ? 'line' : 'bar')">
<option value="bar">Balken</option>
<option value="line">Linie</option>
</select>
</label>
<label class="f">
<span>Quelltabelle</span>
<select :value="chart.sourceTableId ?? ''" @change="store.setChartSource(chart.id, str($event))">
<option v-for="t in tableList" :key="t.id" :value="t.id">{{ t.name }}</option>
</select>
</label>
<div class="grid2">
<label class="f">
<span>Breite (px)</span>
<input type="number" min="200" step="10" :value="chart.width" @change="store.resizeChart(chart.id, num($event), chart.height)" />
</label>
<label class="f">
<span>Höhe (px)</span>
<input type="number" min="140" step="10" :value="chart.height" @change="store.resizeChart(chart.id, chart.width, num($event))" />
</label>
</div>
<div class="grid2">
<label class="f">
<span>Position X</span>
<input type="number" min="0" step="4" :value="chart.x" @change="store.moveObject(chart.id, num($event), chart.y)" />
</label>
<label class="f">
<span>Position Y</span>
<input type="number" min="0" step="4" :value="chart.y" @change="store.moveObject(chart.id, chart.x, num($event))" />
</label>
</div>
<label class="f">
<span>Zahlenformat (Locale)</span>
<select :value="chart.locale ?? ''" @change="store.setObjectLocale(chart.id, str($event) || null)">
<option value="">— vom Blatt / Dokument</option>
<option v-for="l in NUMBER_LOCALES" :key="l.id" :value="l.id">{{ l.label }}</option>
</select>
</label>
<label class="f">
<span>Hintergrund</span>
<span class="color">
<input type="color" :value="chart.bg ?? '#ffffff'" @input="store.setObjectBg(chart.id, str($event))" />
<button class="mini" title="Standard" @click="store.setObjectBg(chart.id, null)">↺</button>
<em v-if="!chart.bg">Design</em>
</span>
</label>
<label class="f">
<span>Text-/Achsenfarbe</span>
<span class="color">
<input type="color" :value="chart.fg ?? '#6e6e73'" @input="store.setObjectTextColor(chart.id, str($event))" />
<button class="mini" title="Standard" @click="store.setObjectTextColor(chart.id, null)">↺</button>
<em v-if="!chart.fg">Design</em>
</span>
</label>
<button class="danger" @click="store.deleteObject(chart.id)">Diagramm löschen</button>
</template>
<template v-else-if="text">
<label class="f">
<span>Name</span>
<input :value="text.name" @change="store.renameObject(text.id, str($event))" />
</label>
<label class="f">
<span>Text</span>
<textarea rows="4" :value="text.text" @change="store.setTextContent(text.id, str($event))" />
</label>
<div class="grid2">
<label class="f">
<span>Schriftgröße (px)</span>
<input type="number" min="9" max="96" :value="text.fontSize" @change="store.setTextFormat(text.id, { fontSize: num($event) })" />
</label>
<label class="f row-inline" style="align-self: end; margin-bottom: 12px">
<button class="tgl" :class="{ on: text.bold }" @click="store.setTextFormat(text.id, { bold: !text.bold })"><b>F</b></button>
<button class="tgl" :class="{ on: text.italic }" @click="store.setTextFormat(text.id, { italic: !text.italic })"><i>K</i></button>
</label>
</div>
<label class="f">
<span>Ausrichtung</span>
<span class="seg">
<button :class="{ on: text.align === 'left' }" @click="store.setTextFormat(text.id, { align: 'left' })">links</button>
<button :class="{ on: text.align === 'center' }" @click="store.setTextFormat(text.id, { align: 'center' })">mitte</button>
<button :class="{ on: text.align === 'right' }" @click="store.setTextFormat(text.id, { align: 'right' })">rechts</button>
</span>
</label>
<label class="f">
<span>Textfarbe</span>
<span class="color">
<input type="color" :value="text.color ?? '#1d1d1f'" @input="store.setObjectTextColor(text.id, str($event))" />
<button class="mini" title="Standard" @click="store.setObjectTextColor(text.id, null)">↺</button>
<em v-if="!text.color">Design</em>
</span>
</label>
<label class="f">
<span>Hintergrund</span>
<span class="color">
<input type="color" :value="text.bg ?? '#ffffff'" @input="store.setObjectBg(text.id, str($event))" />
<button class="mini" title="Ohne" @click="store.setObjectBg(text.id, null)">↺</button>
<em v-if="!text.bg">ohne</em>
</span>
</label>
<div class="grid2">
<label class="f">
<span>Breite (px)</span>
<input type="number" min="80" step="10" :value="text.width" @change="store.resizeText(text.id, num($event), text.height)" />
</label>
<label class="f">
<span>Höhe (px)</span>
<input type="number" min="32" step="10" :value="text.height" @change="store.resizeText(text.id, text.width, num($event))" />
</label>
</div>
<div class="grid2">
<label class="f">
<span>Position X</span>
<input type="number" min="0" step="4" :value="text.x" @change="store.moveObject(text.id, num($event), text.y)" />
</label>
<label class="f">
<span>Position Y</span>
<input type="number" min="0" step="4" :value="text.y" @change="store.moveObject(text.id, text.x, num($event))" />
</label>
</div>
<button class="danger" @click="store.deleteObject(text.id)">Textfeld löschen</button>
</template>
<template v-else-if="button">
<label class="f">
<span>Name</span>
<input :value="button.name" @change="store.renameObject(button.id, str($event))" />
</label>
<label class="f">
<span>Beschriftung</span>
<input :value="button.label" @change="store.setButtonLabel(button.id, str($event))" />
</label>
<label class="f">
<span>Makro</span>
<select :value="button.macroId ?? ''" @change="store.setButtonMacroId(button.id, str($event) || null)">
<option value="">— keins —</option>
<option v-for="m in store.doc.macros" :key="m.id" :value="m.id">{{ m.name }}</option>
</select>
</label>
<p v-if="store.doc.macros.length === 0" class="muted">
Noch kein Makro vorhanden — über „▶ Makros" in der Symbolleiste anlegen.
</p>
<label class="f">
<span>Textfarbe</span>
<span class="color">
<input type="color" :value="button.fg ?? '#0a84ff'" @input="store.setObjectTextColor(button.id, str($event))" />
<button class="mini" title="Standard" @click="store.setObjectTextColor(button.id, null)">↺</button>
<em v-if="!button.fg">Design</em>
</span>
</label>
<label class="f">
<span>Hintergrund</span>
<span class="color">
<input type="color" :value="button.bg ?? '#e8f2ff'" @input="store.setObjectBg(button.id, str($event))" />
<button class="mini" title="Ohne" @click="store.setObjectBg(button.id, null)">↺</button>
<em v-if="!button.bg">Design</em>
</span>
</label>
<div class="grid2">
<label class="f">
<span>Breite (px)</span>
<input type="number" min="60" step="10" :value="button.width" @change="store.resizeButton(button.id, num($event), button.height)" />
</label>
<label class="f">
<span>Höhe (px)</span>
<input type="number" min="28" step="10" :value="button.height" @change="store.resizeButton(button.id, button.width, num($event))" />
</label>
</div>
<div class="grid2">
<label class="f">
<span>Position X</span>
<input type="number" min="0" step="4" :value="button.x" @change="store.moveObject(button.id, num($event), button.y)" />
</label>
<label class="f">
<span>Position Y</span>
<input type="number" min="0" step="4" :value="button.y" @change="store.moveObject(button.id, button.x, num($event))" />
</label>
</div>
<button class="danger" @click="store.deleteObject(button.id)">Button löschen</button>
</template>
<p v-else class="muted">Ein Objekt auf der Fläche anklicken, um es hier zu bearbeiten.</p>
</section>
<!-- ===================== sheet ===================== -->
<section>
<h3>Blatt</h3>
<label class="f">
<span>Name</span>
<input :value="sheet.name" @change="store.renameSheet(sheet.id, str($event))" />
</label>
<label class="f">
<span>Zahlenformat</span>
<select :value="sheet.locale ?? ''" @change="store.setSheetLocale(sheet.id, str($event) || null)">
<option value="">— vom Dokument ({{ store.doc.locale }})</option>
<option v-for="l in NUMBER_LOCALES" :key="l.id" :value="l.id">{{ l.label }}</option>
</select>
</label>
<label class="f">
<span>Hintergrundfarbe</span>
<span class="color">
<input
type="color"
:value="sheet.background ?? '#ffffff'"
@input="store.setSheetBackground(sheet.id, str($event))"
/>
<button class="mini" title="Auf Standard zurücksetzen" @click="store.setSheetBackground(sheet.id, null)">↺</button>
<em v-if="!sheet.background">Design</em>
</span>
</label>
<label class="f">
<span>Schriftfarbe</span>
<span class="color">
<input
type="color"
:value="sheet.foreground ?? '#1d1d1f'"
@input="store.setSheetForeground(sheet.id, str($event))"
/>
<button class="mini" title="Auf Standard zurücksetzen" @click="store.setSheetForeground(sheet.id, null)">↺</button>
<em v-if="!sheet.foreground">Design</em>
</span>
</label>
<label class="f row-inline">
<input
type="checkbox"
:checked="sheet.showGrid !== false"
@change="store.setSheetShowGrid(sheet.id, chk($event))"
/>
<span>Raster einblenden</span>
</label>
<label class="f">
<span>Rasterweite ({{ sheet.gridSize ?? DEFAULT_GRID_SIZE }} px)</span>
<input
type="range"
:min="MIN_GRID_SIZE"
:max="MAX_GRID_SIZE"
:value="sheet.gridSize ?? DEFAULT_GRID_SIZE"
title="Steuert auch, worauf Objekte beim Verschieben/Skalieren einrasten"
@input="store.setSheetGridSize(sheet.id, num($event))"
/>
</label>
<div class="row-btns">
<button @click="store.addSheet()">Neues Blatt</button>
<button class="danger" :disabled="store.doc.sheets.length <= 1" @click="store.deleteSheet(sheet.id)">
Blatt löschen
</button>
</div>
</section>
<!-- ===================== view ===================== -->
<section>
<h3>Ansicht</h3>
<label class="f">
<span>Zoom (dieses Blatt) · {{ Math.round(store.activeZoom.value * 100) }}%</span>
<input
type="range"
min="40"
max="200"
step="5"
:value="Math.round(store.activeZoom.value * 100)"
@input="store.setZoom(num($event) / 100)"
/>
</label>
</section>
<!-- ===================== document ===================== -->
<section>
<h3>Dokument</h3>
<label class="f">
<span>Erscheinungsbild</span>
<div class="seg">
<button
:class="{ on: themeMode === 'system' }"
title="Auto (folgt dem System)"
aria-label="Auto (folgt dem System)"
@click="themeMode = 'system'"
>
<ToolbarIcon name="auto" />
</button>
<button
:class="{ on: themeMode === 'light' }"
title="Hell"
aria-label="Hell"
@click="themeMode = 'light'"
>
<ToolbarIcon name="sun" />
</button>
<button
:class="{ on: themeMode === 'dark' }"
title="Dunkel"
aria-label="Dunkel"
@click="themeMode = 'dark'"
>
<ToolbarIcon name="moon" />
</button>
</div>
</label>
<label class="f">
<span>Zahlenformat (Ländereinstellung)</span>
<select :value="store.doc.locale" @change="store.setDocumentLocale(str($event))">
<option v-for="l in NUMBER_LOCALES" :key="l.id" :value="l.id">{{ l.label }}</option>
</select>
</label>
<label class="f">
<span>CSV-Trennzeichen (Standard)</span>
<select :value="store.doc.csvDelimiter" @change="store.setCsvDelimiter(str($event) === ';' ? ';' : ',')">
<option value=",">Komma ,</option>
<option value=";">Semikolon ;</option>
</select>
</label>
<button class="danger" @click="resetDoc">Auf Beispiel zurücksetzen</button>
</section>
</div>
</aside>
</template>
<style scoped>
.drawer {
position: absolute;
z-index: 60;
top: 0;
left: 0;
bottom: 0;
width: 320px;
max-width: 86vw;
display: flex;
flex-direction: column;
background: var(--surface);
border-right: 1px solid var(--border);
box-shadow: var(--shadow);
transform: translateX(-102%);
transition: transform 0.18s ease;
}
.drawer.open {
transform: none;
}
header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 14px;
border-bottom: 1px solid var(--border);
}
header h2 {
margin: 0;
font-size: 14px;
}
.x {
border: 0;
background: transparent;
font-size: 18px;
cursor: pointer;
color: var(--text-dim);
}
.body {
overflow-y: auto;
padding: 4px 14px 20px;
}
section {
padding: 14px 0;
border-bottom: 1px solid var(--border);
}
section:last-child {
border-bottom: 0;
}
h3 {
margin: 0 0 10px;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-dim);
}
.subsection {
margin: 6px 0 12px;
padding: 10px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--surface-2);
}
.subsection h4 {
margin: 0 0 10px;
font-size: 11px;
color: var(--text-dim);
font-weight: 600;
}
.tgl {
width: 30px;
height: 28px;
border: 1px solid var(--border);
background: var(--surface);
border-radius: 6px;
cursor: pointer;
}
.tgl.on {
background: var(--accent);
color: #fff;
border-color: var(--accent);
}
.f textarea {
font: inherit;
font-size: 13px;
color: var(--text);
background: var(--surface-2);
border: 1px solid var(--border);
border-radius: 7px;
padding: 6px 8px;
resize: vertical;
}
.f {
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: 10px;
font-size: 12px;
color: var(--text-dim);
}
.f input,
.f select {
font: inherit;
font-size: 13px;
color: var(--text);
background: var(--surface-2);
border: 1px solid var(--border);
border-radius: 7px;
padding: 6px 8px;
}
.f input[type='range'] {
padding: 0;
background: transparent;
border: 0;
}
.f.row-inline {
flex-direction: row;
align-items: center;
gap: 8px;
}
.f.row-inline input {
width: auto;
}
.color {
display: flex;
align-items: center;
gap: 8px;
}
.color input[type='color'] {
width: 42px;
height: 28px;
padding: 2px;
cursor: pointer;
}
.color .mini {
border: 1px solid var(--border);
background: var(--surface);
border-radius: 6px;
width: 26px;
height: 26px;
cursor: pointer;
color: var(--text-dim);
}
.color .mini:hover {
border-color: var(--accent);
color: var(--accent);
}
.color em {
font-style: normal;
font-size: 11px;
color: var(--text-dim);
}
.grid2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.row-btns {
display: flex;
gap: 8px;
}
button {
font: inherit;
font-size: 12.5px;
border: 1px solid var(--border);
background: var(--surface);
color: var(--text);
border-radius: 7px;
padding: 6px 12px;
cursor: pointer;
}
button:hover {
border-color: var(--accent);
color: var(--accent);
}
button:disabled {
opacity: 0.45;
cursor: not-allowed;
}
button.danger:hover {
border-color: var(--danger);
color: var(--danger);
}
.seg {
display: inline-flex;
border: 1px solid var(--border);
border-radius: 7px;
overflow: hidden;
}
.seg button {
border: 0;
border-radius: 0;
padding: 5px 10px;
}
.seg button.on {
background: var(--accent);
color: #fff;
}
.muted {
color: var(--text-dim);
font-size: 12px;
margin: 0;
}
</style>