2026-07-03 02:53:29 -05:00
|
|
|
|
<script setup>
|
2026-07-12 08:41:18 -05:00
|
|
|
|
import { computed, ref } from 'vue';
|
2026-07-03 02:53:29 -05:00
|
|
|
|
import Toggle from '@/components/Toggle.vue';
|
2026-07-03 11:48:47 -05:00
|
|
|
|
import { themePresets, buildPalette } from '@/pages/settings/themePresets.js';
|
|
|
|
|
|
import { googleFonts } from '@/pages/settings/googleFonts.js';
|
|
|
|
|
|
|
|
|
|
|
|
const swatchPalette = (preset) => buildPalette(preset);
|
2026-07-03 02:53:29 -05:00
|
|
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
field: { type: Object, required: true },
|
|
|
|
|
|
modelValue: { default: undefined },
|
|
|
|
|
|
// Options for multiselect fields (resolved by the page from optionsSource).
|
|
|
|
|
|
options: { type: Array, default: () => [] },
|
2026-07-12 08:41:18 -05:00
|
|
|
|
// True when this field's `dependsOn` key is falsy — greys out the
|
|
|
|
|
|
// control without hiding it, so the relationship stays visible.
|
|
|
|
|
|
disabled: { type: Boolean, default: false },
|
|
|
|
|
|
// Shared REST fetcher, only used by field types that need their own API
|
|
|
|
|
|
// calls beyond the generic wp/v2/settings save flow (companion-plugin).
|
|
|
|
|
|
api: { type: Function, default: null },
|
2026-07-03 02:53:29 -05:00
|
|
|
|
});
|
|
|
|
|
|
const emit = defineEmits(['update:modelValue']);
|
|
|
|
|
|
|
|
|
|
|
|
const val = computed({
|
|
|
|
|
|
get: () => props.modelValue,
|
|
|
|
|
|
set: (v) => emit('update:modelValue', v),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Repeater helpers ---------------------------------------------------------
|
|
|
|
|
|
const rows = computed(() => (Array.isArray(props.modelValue) ? props.modelValue : []));
|
|
|
|
|
|
const addRow = () => emit('update:modelValue', [...rows.value, { find: '', replace: '' }]);
|
|
|
|
|
|
const updateRow = (i, key, v) => {
|
|
|
|
|
|
const next = rows.value.map((r, idx) => (idx === i ? { ...r, [key]: v } : r));
|
|
|
|
|
|
emit('update:modelValue', next);
|
|
|
|
|
|
};
|
|
|
|
|
|
const removeRow = (i) => emit('update:modelValue', rows.value.filter((_, idx) => idx !== i));
|
|
|
|
|
|
|
|
|
|
|
|
// Multiselect helpers -------------------------------------------------------
|
|
|
|
|
|
const selectedValues = computed(() =>
|
|
|
|
|
|
Array.isArray(props.modelValue) ? props.modelValue : []
|
|
|
|
|
|
);
|
|
|
|
|
|
const toggleValue = (value) => {
|
|
|
|
|
|
const next = selectedValues.value.includes(value)
|
|
|
|
|
|
? selectedValues.value.filter((v) => v !== value)
|
|
|
|
|
|
: [...selectedValues.value, value];
|
|
|
|
|
|
emit('update:modelValue', next);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Media picker (image fields) ---------------------------------------------
|
|
|
|
|
|
const pickImage = () => {
|
|
|
|
|
|
const wp = window.wp;
|
|
|
|
|
|
if (!wp || !wp.media) {
|
|
|
|
|
|
// Fallback: prompt for a URL if the media library isn't available.
|
|
|
|
|
|
const url = window.prompt('Image URL');
|
|
|
|
|
|
if (url) emit('update:modelValue', url);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const frame = wp.media({ title: props.field.label, multiple: false });
|
|
|
|
|
|
frame.on('select', () => {
|
|
|
|
|
|
const att = frame.state().get('selection').first().toJSON();
|
|
|
|
|
|
emit('update:modelValue', att.url);
|
|
|
|
|
|
});
|
|
|
|
|
|
frame.open();
|
|
|
|
|
|
};
|
2026-07-03 11:48:47 -05:00
|
|
|
|
|
|
|
|
|
|
// Font picker (searchable Google Font dropdown) ----------------------------
|
|
|
|
|
|
const fontQuery = ref('');
|
|
|
|
|
|
const fontOpen = ref(false);
|
|
|
|
|
|
const filteredFonts = computed(() => {
|
|
|
|
|
|
const q = fontQuery.value.trim().toLowerCase();
|
|
|
|
|
|
const list = q ? googleFonts.filter((f) => f.toLowerCase().includes(q)) : googleFonts;
|
|
|
|
|
|
return list.slice(0, 40);
|
|
|
|
|
|
});
|
|
|
|
|
|
const pickFont = (name) => {
|
|
|
|
|
|
emit('update:modelValue', name);
|
|
|
|
|
|
fontQuery.value = name;
|
|
|
|
|
|
fontOpen.value = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
const openFontList = () => {
|
|
|
|
|
|
fontQuery.value = props.modelValue || '';
|
|
|
|
|
|
fontOpen.value = true;
|
|
|
|
|
|
};
|
2026-07-12 08:41:18 -05:00
|
|
|
|
|
|
|
|
|
|
// Companion plugin (Aurora File Manager) install card -----------------------
|
|
|
|
|
|
const companionStatus = ref(null); // { installed, active } | null while loading
|
|
|
|
|
|
const companionBusy = ref(false);
|
|
|
|
|
|
const companionError = ref('');
|
|
|
|
|
|
|
|
|
|
|
|
const loadCompanionStatus = async () => {
|
|
|
|
|
|
if (props.field.type !== 'companion-plugin' || !props.api) return;
|
|
|
|
|
|
try {
|
|
|
|
|
|
companionStatus.value = await props.api(props.field.statusEndpoint);
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
companionStatus.value = { installed: false, active: false };
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
if (props.field.type === 'companion-plugin') {
|
|
|
|
|
|
loadCompanionStatus();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const installCompanion = async () => {
|
|
|
|
|
|
if (!props.api) return;
|
|
|
|
|
|
companionBusy.value = true;
|
|
|
|
|
|
companionError.value = '';
|
|
|
|
|
|
try {
|
|
|
|
|
|
const result = await props.api(props.field.installEndpoint, { method: 'POST' });
|
|
|
|
|
|
if (!result.success) {
|
|
|
|
|
|
companionError.value = result.message || 'Installation failed.';
|
|
|
|
|
|
}
|
|
|
|
|
|
await loadCompanionStatus();
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
companionError.value = e.message || 'Installation failed.';
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
companionBusy.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2026-07-03 02:53:29 -05:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
2026-07-12 08:41:18 -05:00
|
|
|
|
<div class="field">
|
2026-07-03 02:53:29 -05:00
|
|
|
|
<div class="field__info">
|
|
|
|
|
|
<div class="field__label">{{ field.label }}</div>
|
|
|
|
|
|
<p v-if="field.description" class="field__desc">{{ field.description }}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="field__control">
|
|
|
|
|
|
<!-- Text -->
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-if="field.type === 'text'"
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
class="field__input"
|
|
|
|
|
|
:placeholder="field.placeholder || ''"
|
2026-07-12 08:41:18 -05:00
|
|
|
|
:readonly="!!field.readonly"
|
2026-07-03 02:53:29 -05:00
|
|
|
|
:value="val || ''"
|
|
|
|
|
|
@input="val = $event.target.value"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
2026-07-12 08:41:18 -05:00
|
|
|
|
<!-- Textarea (multi-line plain text — moderation/blocklist fields) -->
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
v-else-if="field.type === 'textarea'"
|
|
|
|
|
|
class="field__input field__textarea"
|
|
|
|
|
|
:placeholder="field.placeholder || ''"
|
|
|
|
|
|
:rows="field.rows || 6"
|
|
|
|
|
|
:value="val || ''"
|
|
|
|
|
|
@input="val = $event.target.value"
|
|
|
|
|
|
></textarea>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Select (plain dropdown, distinct from the multiselect checkbox list below) -->
|
|
|
|
|
|
<select
|
|
|
|
|
|
v-else-if="field.type === 'select'"
|
|
|
|
|
|
class="field__input"
|
|
|
|
|
|
:value="val ?? ''"
|
|
|
|
|
|
:disabled="disabled"
|
|
|
|
|
|
@change="val = $event.target.value"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option v-for="opt in (field.options || options)" :key="opt.value" :value="opt.value">
|
|
|
|
|
|
{{ opt.label }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
|
2026-07-03 02:53:29 -05:00
|
|
|
|
<!-- Toggle -->
|
|
|
|
|
|
<Toggle
|
|
|
|
|
|
v-else-if="field.type === 'toggle'"
|
|
|
|
|
|
:model-value="!!val"
|
2026-07-12 08:41:18 -05:00
|
|
|
|
:disabled="disabled"
|
2026-07-03 02:53:29 -05:00
|
|
|
|
@update:model-value="val = $event"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Color -->
|
|
|
|
|
|
<div v-else-if="field.type === 'color'" class="field__color">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="color"
|
|
|
|
|
|
:value="val || field.default || '#4772fa'"
|
|
|
|
|
|
@input="val = $event.target.value"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span class="field__color-hex">{{ val || field.default || '#4772fa' }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Image / favicon -->
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-else-if="field.type === 'image'"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="field__image"
|
|
|
|
|
|
@click="pickImage"
|
|
|
|
|
|
>
|
|
|
|
|
|
<img v-if="val" :src="val" alt="" />
|
|
|
|
|
|
<span v-else class="dashicons dashicons-format-image field__image-ph" />
|
2026-07-04 03:45:28 -05:00
|
|
|
|
<span class="field__image-label">{{ val ? 'Change' : 'Choose from media library' }}</span>
|
2026-07-03 02:53:29 -05:00
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Multiselect (checkbox list) -->
|
|
|
|
|
|
<div v-else-if="field.type === 'multiselect'" class="field__multi">
|
|
|
|
|
|
<label v-for="opt in options" :key="opt.value" class="field__multi-item">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
:checked="selectedValues.includes(opt.value)"
|
|
|
|
|
|
@change="toggleValue(opt.value)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span>{{ opt.label }}</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<p v-if="!options.length" class="field__multi-empty">No options available.</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Segmented No/Yes -->
|
|
|
|
|
|
<div v-else-if="field.type === 'segmented'" class="field__seg">
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:class="{ 'field__seg--active': !val }"
|
|
|
|
|
|
@click="val = false"
|
|
|
|
|
|
>
|
|
|
|
|
|
No
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:class="{ 'field__seg--active': !!val }"
|
|
|
|
|
|
@click="val = true"
|
|
|
|
|
|
>
|
|
|
|
|
|
Yes
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-03 11:48:47 -05:00
|
|
|
|
<!-- Theme picker (10 fixed color presets, whole-UI preview) -->
|
|
|
|
|
|
<div v-else-if="field.type === 'theme-picker'" class="field__theme">
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="preset in themePresets"
|
|
|
|
|
|
:key="preset.id"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="field__theme-card"
|
|
|
|
|
|
:class="{ 'field__theme-card--active': (val || field.default) === preset.id }"
|
|
|
|
|
|
@click="val = preset.id"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span
|
|
|
|
|
|
class="field__theme-swatch"
|
|
|
|
|
|
:style="{
|
|
|
|
|
|
background: swatchPalette(preset)['--aurora-app-bg'],
|
|
|
|
|
|
borderColor: swatchPalette(preset)['--aurora-border'],
|
|
|
|
|
|
}"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span
|
|
|
|
|
|
class="field__theme-swatch-accent"
|
|
|
|
|
|
:style="{ background: swatchPalette(preset)['--aurora-accent'] }"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span class="field__theme-name">{{ preset.label }}</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Font picker (searchable Google Font dropdown) -->
|
|
|
|
|
|
<div v-else-if="field.type === 'font-picker'" class="field__font">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
class="field__input"
|
|
|
|
|
|
placeholder="Search Google Fonts…"
|
|
|
|
|
|
:value="fontOpen ? fontQuery : (val || 'Default')"
|
|
|
|
|
|
@focus="openFontList"
|
|
|
|
|
|
@input="fontQuery = $event.target.value"
|
|
|
|
|
|
@blur="() => setTimeout(() => (fontOpen = false), 150)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div v-if="fontOpen" class="field__font-list">
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="name in filteredFonts"
|
|
|
|
|
|
:key="name"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="field__font-item"
|
|
|
|
|
|
:class="{ 'field__font-item--active': val === name }"
|
|
|
|
|
|
:style="{ fontFamily: `'${name}', sans-serif` }"
|
|
|
|
|
|
@mousedown.prevent="pickFont(name)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ name }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<p v-if="!filteredFonts.length" class="field__multi-empty">No fonts match.</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-07-12 08:41:18 -05:00
|
|
|
|
<!-- Companion plugin install card -->
|
|
|
|
|
|
<div v-else-if="field.type === 'companion-plugin'" class="field__companion">
|
|
|
|
|
|
<div v-if="!companionStatus" class="field__companion-status">Checking…</div>
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
<a
|
|
|
|
|
|
v-if="companionStatus.active"
|
|
|
|
|
|
class="field__companion-manage"
|
|
|
|
|
|
:href="field.manageUrl"
|
|
|
|
|
|
>
|
|
|
|
|
|
Installed & active — open File Manager →
|
|
|
|
|
|
</a>
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="field__companion-install"
|
|
|
|
|
|
:disabled="companionBusy"
|
|
|
|
|
|
@click="installCompanion"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ companionBusy ? 'Installing…' : (companionStatus.installed ? 'Activate' : 'Install & Activate') }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<p v-if="companionError" class="field__companion-error">{{ companionError }}</p>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-07-03 02:53:29 -05:00
|
|
|
|
<!-- Repeater -->
|
|
|
|
|
|
<div v-else-if="field.type === 'repeater'" class="field__repeater">
|
|
|
|
|
|
<div v-for="(row, i) in rows" :key="i" class="field__repeater-row">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
class="field__input"
|
|
|
|
|
|
:placeholder="field.columns ? field.columns[0] : 'Find'"
|
|
|
|
|
|
:value="row.find"
|
|
|
|
|
|
@input="updateRow(i, 'find', $event.target.value)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
class="field__input"
|
|
|
|
|
|
:placeholder="field.columns ? field.columns[1] : 'Replace'"
|
|
|
|
|
|
:value="row.replace"
|
|
|
|
|
|
@input="updateRow(i, 'replace', $event.target.value)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<button type="button" class="field__repeater-remove" @click="removeRow(i)">×</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button type="button" class="field__newitem" @click="addRow">New item</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.field {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
|
|
|
|
gap: 32px;
|
|
|
|
|
|
padding: 28px 0;
|
|
|
|
|
|
border-bottom: 1px solid var(--aurora-border);
|
|
|
|
|
|
align-items: start;
|
|
|
|
|
|
}
|
|
|
|
|
|
.field__label { font-size: 0.95rem; font-weight: 600; color: var(--aurora-text); }
|
|
|
|
|
|
.field__desc { margin: 6px 0 0; font-size: 0.8125rem; color: var(--aurora-text-muted); line-height: 1.5; }
|
|
|
|
|
|
.field__control { display: flex; justify-content: flex-start; }
|
|
|
|
|
|
|
|
|
|
|
|
.field__input {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
background: var(--aurora-bg-subtle);
|
|
|
|
|
|
border: 1px solid var(--aurora-border);
|
|
|
|
|
|
color: var(--aurora-text);
|
2026-07-12 13:21:22 -05:00
|
|
|
|
border-radius: var(--aurora-radius-sm);
|
2026-07-03 02:53:29 -05:00
|
|
|
|
padding: 9px 12px;
|
|
|
|
|
|
font-size: 0.875rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
.field__input:focus { outline: none; border-color: var(--aurora-accent); }
|
2026-07-12 08:41:18 -05:00
|
|
|
|
.field__textarea { resize: vertical; font-family: inherit; line-height: 1.5; }
|
2026-07-03 02:53:29 -05:00
|
|
|
|
|
|
|
|
|
|
.field__color { display: flex; align-items: center; gap: 10px; }
|
|
|
|
|
|
.field__color input[type='color'] {
|
|
|
|
|
|
width: 40px; height: 32px; border: 1px solid var(--aurora-border);
|
2026-07-12 13:21:22 -05:00
|
|
|
|
border-radius: var(--aurora-radius-sm); background: none; cursor: pointer; padding: 2px;
|
2026-07-03 02:53:29 -05:00
|
|
|
|
}
|
|
|
|
|
|
.field__color-hex { font-size: 0.8125rem; color: var(--aurora-text-muted); }
|
|
|
|
|
|
|
|
|
|
|
|
.field__image {
|
|
|
|
|
|
width: 160px; height: 120px;
|
|
|
|
|
|
border: 1px dashed var(--aurora-border);
|
2026-07-12 13:21:22 -05:00
|
|
|
|
border-radius: var(--aurora-radius-md);
|
2026-07-03 02:53:29 -05:00
|
|
|
|
background: var(--aurora-bg-subtle);
|
|
|
|
|
|
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
|
|
|
|
gap: 8px; cursor: pointer; color: var(--aurora-text-muted);
|
|
|
|
|
|
position: relative; overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
.field__image img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
|
|
|
|
|
|
.field__image-ph { font-size: 28px; width: 28px; height: 28px; }
|
|
|
|
|
|
.field__image-label {
|
|
|
|
|
|
position: absolute; bottom: 0; left: 0; right: 0;
|
|
|
|
|
|
background: var(--aurora-card-bg); padding: 6px; font-size: 0.75rem;
|
|
|
|
|
|
border-top: 1px solid var(--aurora-border);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.field__repeater { width: 100%; }
|
|
|
|
|
|
.field__repeater-row { display: flex; gap: 8px; margin-bottom: 8px; }
|
|
|
|
|
|
.field__repeater-remove {
|
|
|
|
|
|
flex-shrink: 0; width: 34px; border: 1px solid var(--aurora-border);
|
|
|
|
|
|
background: var(--aurora-bg-subtle); color: var(--aurora-text-muted);
|
2026-07-12 13:21:22 -05:00
|
|
|
|
border-radius: var(--aurora-radius-sm); cursor: pointer; font-size: 1.1rem;
|
2026-07-03 02:53:29 -05:00
|
|
|
|
}
|
|
|
|
|
|
.field__newitem {
|
|
|
|
|
|
border: 1px solid var(--aurora-border);
|
|
|
|
|
|
background: var(--aurora-bg-subtle);
|
|
|
|
|
|
color: var(--aurora-text);
|
2026-07-12 13:21:22 -05:00
|
|
|
|
border-radius: var(--aurora-radius-sm); padding: 8px 16px; font-size: 0.8125rem; cursor: pointer;
|
2026-07-03 02:53:29 -05:00
|
|
|
|
}
|
|
|
|
|
|
.field__newitem:hover { border-color: var(--aurora-accent); color: var(--aurora-accent); }
|
|
|
|
|
|
|
|
|
|
|
|
/* Multiselect */
|
|
|
|
|
|
.field__multi { display: flex; flex-direction: column; gap: 8px; width: 100%; }
|
|
|
|
|
|
.field__multi-item {
|
|
|
|
|
|
display: flex; align-items: center; gap: 8px;
|
|
|
|
|
|
font-size: 0.8125rem; color: var(--aurora-text); cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
.field__multi-item input { accent-color: var(--aurora-accent); }
|
|
|
|
|
|
.field__multi-empty { font-size: 0.8125rem; color: var(--aurora-text-muted); margin: 0; }
|
|
|
|
|
|
|
|
|
|
|
|
/* Segmented No/Yes */
|
|
|
|
|
|
.field__seg {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
background: var(--aurora-bg-subtle);
|
|
|
|
|
|
border: 1px solid var(--aurora-border);
|
2026-07-12 13:21:22 -05:00
|
|
|
|
border-radius: var(--aurora-radius-md);
|
2026-07-03 02:53:29 -05:00
|
|
|
|
padding: 3px;
|
|
|
|
|
|
gap: 2px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.field__seg button {
|
|
|
|
|
|
border: none; background: none; cursor: pointer;
|
2026-07-12 13:21:22 -05:00
|
|
|
|
padding: 6px 26px; border-radius: var(--aurora-radius-sm);
|
2026-07-03 02:53:29 -05:00
|
|
|
|
font-size: 0.8125rem; color: var(--aurora-text-muted);
|
|
|
|
|
|
}
|
|
|
|
|
|
.field__seg--active { background: var(--aurora-accent); color: #fff !important; }
|
|
|
|
|
|
|
2026-07-03 11:48:47 -05:00
|
|
|
|
/* Theme picker */
|
|
|
|
|
|
.field__theme { display: flex; flex-wrap: wrap; gap: 10px; width: 100%; }
|
|
|
|
|
|
.field__theme-card {
|
|
|
|
|
|
display: flex; flex-direction: column; align-items: center; gap: 6px;
|
|
|
|
|
|
width: 76px; padding: 10px 6px;
|
|
|
|
|
|
border: 1px solid var(--aurora-border);
|
2026-07-12 13:21:22 -05:00
|
|
|
|
border-radius: var(--aurora-radius-md);
|
2026-07-03 11:48:47 -05:00
|
|
|
|
background: var(--aurora-bg-subtle);
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
.field__theme-card--active { border-color: var(--aurora-accent); box-shadow: 0 0 0 1px var(--aurora-accent); }
|
|
|
|
|
|
.field__theme-swatch {
|
2026-07-12 13:21:22 -05:00
|
|
|
|
width: 36px; height: 36px; border-radius: var(--aurora-radius-md);
|
2026-07-03 11:48:47 -05:00
|
|
|
|
border: 1px solid var(--aurora-card-border);
|
|
|
|
|
|
display: flex; align-items: flex-end; justify-content: flex-end;
|
|
|
|
|
|
padding: 4px;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
}
|
|
|
|
|
|
.field__theme-swatch-accent {
|
|
|
|
|
|
width: 10px; height: 10px; border-radius: 50%;
|
|
|
|
|
|
}
|
|
|
|
|
|
.field__theme-name { font-size: 0.6875rem; color: var(--aurora-text-muted); }
|
|
|
|
|
|
|
|
|
|
|
|
/* Font picker */
|
|
|
|
|
|
.field__font { position: relative; width: 100%; max-width: 320px; }
|
|
|
|
|
|
.field__font-list {
|
|
|
|
|
|
position: absolute; top: calc(100% + 4px); left: 0; right: 0;
|
|
|
|
|
|
max-height: 260px; overflow-y: auto; z-index: 20;
|
|
|
|
|
|
background: var(--aurora-card-bg);
|
|
|
|
|
|
border: 1px solid var(--aurora-border);
|
2026-07-12 13:21:22 -05:00
|
|
|
|
border-radius: var(--aurora-radius-md);
|
2026-07-03 11:48:47 -05:00
|
|
|
|
padding: 6px;
|
|
|
|
|
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
|
|
|
|
|
|
}
|
|
|
|
|
|
.field__font-item {
|
|
|
|
|
|
display: block; width: 100%; text-align: left;
|
|
|
|
|
|
border: none; background: none; cursor: pointer;
|
2026-07-12 13:21:22 -05:00
|
|
|
|
padding: 8px 10px; border-radius: var(--aurora-radius-sm);
|
2026-07-03 11:48:47 -05:00
|
|
|
|
font-size: 0.875rem; color: var(--aurora-text);
|
|
|
|
|
|
}
|
|
|
|
|
|
.field__font-item:hover { background: var(--aurora-bg-subtle); }
|
|
|
|
|
|
.field__font-item--active { color: var(--aurora-accent); }
|
|
|
|
|
|
|
2026-07-12 08:41:18 -05:00
|
|
|
|
/* Companion plugin install card */
|
|
|
|
|
|
.field__companion-status { font-size: 0.8125rem; color: var(--aurora-text-muted); }
|
|
|
|
|
|
.field__companion-install {
|
2026-07-12 13:21:22 -05:00
|
|
|
|
border: none; border-radius: var(--aurora-radius-sm); background: var(--aurora-accent); color: #fff;
|
2026-07-12 08:41:18 -05:00
|
|
|
|
padding: 8px 16px; font-size: 0.8125rem; font-weight: 600; cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
.field__companion-install:disabled { opacity: 0.6; cursor: default; }
|
|
|
|
|
|
.field__companion-manage {
|
|
|
|
|
|
display: inline-block; font-size: 0.8125rem; font-weight: 600;
|
|
|
|
|
|
color: var(--aurora-accent); text-decoration: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.field__companion-manage:hover { text-decoration: underline; }
|
|
|
|
|
|
.field__companion-error { margin: 8px 0 0; font-size: 0.8125rem; color: #e5484d; }
|
|
|
|
|
|
|
2026-07-03 02:53:29 -05:00
|
|
|
|
@media (max-width: 782px) {
|
|
|
|
|
|
.field { grid-template-columns: 1fr; gap: 12px; }
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|