Add desktop Batch toggle, fix xAIGen library hide/selection, per-engine steps/CFG, and mobile prompt editor.
Desktop Batch splits only on double blank lines when enabled; library hide actually conceals thumbs and pinned clicks stick; steps/CFG restore per image model; mobile opens a near-fullscreen prompt editor.
This commit is contained in:
@@ -124,12 +124,16 @@
|
||||
ref="promptArea"
|
||||
v-model="form.promptSections.action"
|
||||
class="field-area"
|
||||
placeholder="Describe the image or the shot. Blank line between prompts for a batch."
|
||||
:placeholder="batchMode ? 'Separate prompts with two blank lines.' : 'Describe the image or the shot.'"
|
||||
@select="capturePromptCaret"
|
||||
@click="capturePromptCaret"
|
||||
@keyup="capturePromptCaret"
|
||||
/>
|
||||
<p v-if="imagePromptCount > 1" class="mt-1 text-[var(--type-sm)] text-[var(--muted)]">{{ imagePromptCount }} prompts · queued in order</p>
|
||||
<label v-if="!video && form.mode==='generate'" class="mt-2 inline-flex items-center gap-2 text-[var(--type-sm)] text-[var(--muted)]">
|
||||
<input v-model="batchMode" type="checkbox" class="accent-[var(--amber)]">Batch
|
||||
</label>
|
||||
<p v-if="batchMode && imagePromptCount > 1" class="mt-1 text-[var(--type-sm)] text-[var(--muted)]">{{ imagePromptCount }} prompts · queued in order</p>
|
||||
<p v-else-if="batchMode" class="mt-1 text-[11px] text-[var(--muted)]">Two blank lines between prompts</p>
|
||||
</label>
|
||||
<label v-if="form.engine==='qwen21' && (form.mode==='generate' || form.mode==='edit')" class="inline-flex items-center gap-2 text-[var(--type-sm)] text-[var(--muted)]">
|
||||
<input v-model="form.enhancePrompt" type="checkbox" class="accent-[var(--amber)]">Enhance prompt
|
||||
@@ -353,6 +357,7 @@
|
||||
:selected="selected"
|
||||
:folder-id="form.folderId"
|
||||
:preview-hidden="!eyeOpen"
|
||||
:is-hidden="isHidden"
|
||||
@close="libraryOpen=false"
|
||||
@select="selectOutput"
|
||||
@use-image="onLibraryUseImage"
|
||||
@@ -407,7 +412,7 @@
|
||||
<div class="queue-foot">
|
||||
<button type="button" class="generate-cta" :disabled="submitting" @click="queue">
|
||||
<span aria-hidden="true">▶</span>
|
||||
{{ submitting ? 'Queuing…' : (imagePromptCount > 1 ? `Generate · ${imagePromptCount} prompts` : 'Generate') }}
|
||||
{{ submitting ? 'Queuing…' : (batchMode && imagePromptCount > 1 ? `Generate · ${imagePromptCount} prompts` : 'Generate') }}
|
||||
</button>
|
||||
<p v-if="error" class="queue-foot-error">{{ error }}</p>
|
||||
<p class="tiny">
|
||||
@@ -481,7 +486,7 @@ import {queueJobs,jobStageLabel,jobProgressView,elapsedSeconds} from '~/shared/s
|
||||
import {gpuStatus} from '~/shared/studio2/status.mjs'
|
||||
import {CAPTION_STYLES} from '~/shared/caption.mjs'
|
||||
import {applyUploadedStill} from '~/shared/studio2/upload.mjs'
|
||||
import {defaultNegatives,parseBatch} from '~/shared/studio2/styles.mjs'
|
||||
import {defaultNegatives,parseBatch,parseBatchDoubleBlank} from '~/shared/studio2/styles.mjs'
|
||||
import {randomSeed} from '~/shared/studio2/seed.mjs'
|
||||
import {
|
||||
photosPlan,
|
||||
@@ -553,6 +558,8 @@ function benchDraftPayload(){
|
||||
fps:form.settings?.fps,
|
||||
loraStack:structuredClone(form.settings?.loraStack||[])
|
||||
},
|
||||
engineSampling:structuredClone(engineSampling),
|
||||
batchMode:!!batchMode.value,
|
||||
eyeOpen:!!eyeOpen.value,
|
||||
hideInputs:!!hideInputs.value,
|
||||
music:structuredClone({tags:music.tags,lyrics:music.lyrics,duration:music.duration,seed:music.seed,name:music.name})
|
||||
@@ -597,6 +604,15 @@ function restoreBenchDraft(){
|
||||
loraStack:Array.isArray(draft.settings.loraStack)?draft.settings.loraStack:form.settings.loraStack
|
||||
})
|
||||
}
|
||||
if(draft.engineSampling && typeof draft.engineSampling==='object'){
|
||||
for(const key of ['flux','krea','qwen21'] as const){
|
||||
const row=draft.engineSampling[key]
|
||||
if(row && Number.isFinite(Number(row.steps)) && Number.isFinite(Number(row.cfg))){
|
||||
engineSampling[key]={steps:Number(row.steps),cfg:Number(row.cfg)}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(draft.batchMode!=null)batchMode.value=!!draft.batchMode
|
||||
if(draft.eyeOpen!=null)eyeOpen.value=!!draft.eyeOpen
|
||||
if(draft.hideInputs!=null)hideInputs.value=!!draft.hideInputs
|
||||
if(draft.music){
|
||||
@@ -644,6 +660,13 @@ watch(()=>[currentLoraUse.value,form.settings.loraStack],()=>loraMemory.sync(for
|
||||
const route=useRoute()
|
||||
const tasks=['generate','edit','compose','refine','video','extend','music']
|
||||
const settingsOpen=ref(false),detailsOpen=ref(false),submitting=ref(false),error=ref(''),batchText=ref('')
|
||||
const batchMode=ref(false)
|
||||
const selectionPinned=ref(false)
|
||||
const engineSampling=reactive<Record<string,{steps:number;cfg:number}>>({
|
||||
flux:{steps:24,cfg:4},
|
||||
krea:{steps:24,cfg:4},
|
||||
qwen21:{steps:25,cfg:1}
|
||||
})
|
||||
const viewerVideo=ref<HTMLVideoElement|null>(null)
|
||||
const viewerMuted=ref(false)
|
||||
const videoTime=ref(0)
|
||||
@@ -665,14 +688,32 @@ const engineOptions=computed(()=>{
|
||||
{value:'qwen21',label:'Qwen 2.1',disabled:!qwenAllowed.value,hint:qwenAllowed.value?'':'Generate and Edit only'}
|
||||
]
|
||||
})
|
||||
function defaultSamplingFor(engine:string){
|
||||
if(engine==='qwen21') return {steps:form.turbo?6:25,cfg:1}
|
||||
if(engine==='minimax' || engine==='ltx') return {steps:8,cfg:1.5}
|
||||
return {steps:24,cfg:4}
|
||||
}
|
||||
function rememberEngineSampling(engine:string){
|
||||
if(!['flux','krea','qwen21'].includes(engine)) return
|
||||
engineSampling[engine]={
|
||||
steps:Number(form.settings.steps)||defaultSamplingFor(engine).steps,
|
||||
cfg:Number(form.settings.cfg)||defaultSamplingFor(engine).cfg
|
||||
}
|
||||
}
|
||||
function applyEngineSampling(engine:string){
|
||||
const saved=engineSampling[engine]
|
||||
const fallback=defaultSamplingFor(engine)
|
||||
form.settings.steps=saved?.steps ?? fallback.steps
|
||||
form.settings.cfg=saved?.cfg ?? fallback.cfg
|
||||
}
|
||||
watch(engineOptions,opts=>{
|
||||
const current=opts.find(o=>o.value===form.engine)
|
||||
if(!current || current.disabled)form.engine=opts.find(o=>!o.disabled)?.value || form.engine
|
||||
})
|
||||
watch(()=>form.engine,(engine,prev)=>{
|
||||
if(engine==='qwen21' && prev!=='qwen21'){
|
||||
form.settings.steps=form.turbo?6:25
|
||||
form.settings.cfg=1
|
||||
if(prev && prev!==engine) rememberEngineSampling(prev)
|
||||
if(['flux','krea','qwen21'].includes(engine)) applyEngineSampling(engine)
|
||||
if(engine==='qwen21'){
|
||||
form.lockFace=false
|
||||
form.lockOutfit=false
|
||||
}
|
||||
@@ -683,11 +724,13 @@ watch(()=>form.turbo,on=>{
|
||||
if(form.engine!=='qwen21')return
|
||||
form.settings.steps=on?6:25
|
||||
form.settings.cfg=1
|
||||
rememberEngineSampling('qwen21')
|
||||
})
|
||||
watch(()=>[form.settings.steps,form.settings.cfg],()=>{
|
||||
if(['flux','krea','qwen21'].includes(form.engine)) rememberEngineSampling(form.engine)
|
||||
})
|
||||
watch(()=>[form.mode,form.engine],([mode,engine])=>{
|
||||
if(engine==='qwen21' && mode==='edit'){
|
||||
form.settings.steps=form.turbo?6:25
|
||||
form.settings.cfg=1
|
||||
form.lockFace=false
|
||||
form.lockOutfit=false
|
||||
}
|
||||
@@ -708,6 +751,7 @@ watch(()=>[form.mode,form.engine],([mode,engine])=>{
|
||||
})
|
||||
watch(()=>form.folderId,(id,prev)=>{
|
||||
selected.value=null
|
||||
selectionPinned.value=false
|
||||
// Wipe Photos only when leaving an existing folder — first restore from '' must keep the draft
|
||||
if(prev){
|
||||
form.imageAId=''
|
||||
@@ -745,10 +789,10 @@ function onStagePreviewError(){
|
||||
watch(()=>selected.value?.id,()=>{stagePreviewNonce.value=0;stagePreviewRetries.value=0})
|
||||
const folderJobs=computed(()=>jobs.value.filter(j=>j.request.folderId===form.folderId))
|
||||
const benchJobs=computed(()=>queueJobs(folderJobs.value))
|
||||
/** Desktop image generate: blank-line paragraphs in the prompt box (same parseBatch as mobile batchText). */
|
||||
/** Desktop image Batch: double blank line only, and only when the toggle is on. */
|
||||
const imagePromptParts=computed(()=>{
|
||||
if(video.value || form.mode!=='generate') return [] as string[]
|
||||
return parseBatch(form.promptSections?.action)
|
||||
if(video.value || form.mode!=='generate' || !batchMode.value) return [] as string[]
|
||||
return parseBatchDoubleBlank(form.promptSections?.action)
|
||||
})
|
||||
const imagePromptCount=computed(()=>imagePromptParts.value.length)
|
||||
const musicJobs=computed(()=>studioQueue.value
|
||||
@@ -1410,12 +1454,23 @@ function chooseTask(task:string){
|
||||
}
|
||||
return
|
||||
}
|
||||
const was=video.value;form.mode=task;form.startClipId='';if(video.value!==was){form.engine=video.value?(xaigen && form.engine==='ltx'?'ltx':'minimax'):'flux';form.settings.steps=video.value?8:24;form.settings.cfg=video.value?1.5:4}
|
||||
const was=video.value;form.mode=task;form.startClipId=''
|
||||
if(video.value!==was){
|
||||
if(was && ['flux','krea','qwen21'].includes(form.engine)) rememberEngineSampling(form.engine)
|
||||
form.engine=video.value?(xaigen && form.engine==='ltx'?'ltx':'minimax'):'flux'
|
||||
if(video.value){form.settings.steps=8;form.settings.cfg=1.5}
|
||||
else applyEngineSampling(form.engine)
|
||||
}
|
||||
if(selected.value?.kind==='track' && task!=='music') selected.value=family.value.find((a:any)=>a.kind!=='track') || null
|
||||
}
|
||||
function onTaskSelect(event:Event){chooseTask((event.target as HTMLSelectElement).value)}
|
||||
function chooseClip(id:string){if(!id){form.startClipId='';return}const clip=clips.value.find((c:any)=>c.id===id);if(clip){try{Object.assign(form,bindClip(form,clip,xaigen,loraMemory))}catch(e:any){error.value=e?.message||'Could not copy clip settings.';form.startClipId=clip.id;form.mode='extend';form.engine=xaigen && form.engine==='ltx'?'ltx':'minimax'}}}
|
||||
function selectOutput(item:any){stageMenu.value='';selected.value=item;if(form.mode==='extend' && item?.kind==='clip')chooseClip(item.id)}
|
||||
function selectOutput(item:any){
|
||||
stageMenu.value=''
|
||||
selected.value=item
|
||||
selectionPinned.value=!!item
|
||||
if(form.mode==='extend' && item?.kind==='clip')chooseClip(item.id)
|
||||
}
|
||||
function restore(item:any){Object.assign(form,restoreAll(form,item,xaigen,loraMemory));form.guides=form.guides||[];batchText.value=''}
|
||||
function focusJob(job:any){selectOutput(job.outputs?.at(-1)||null);focusedJobId.value=job.id}
|
||||
async function loadLoras(fresh=false){try{loraNames.value=await $fetch('/api/studio-2/loras'+(fresh?'?fresh=1':''))}catch{error.value='Could not refresh LoRAs. The last loaded list is still available.'}}
|
||||
@@ -1484,16 +1539,24 @@ function ingestNewOutputs(nextJobs:any[]){
|
||||
}
|
||||
}
|
||||
if(fresh){
|
||||
const existingFresh=family.value.find(a=>a.id===fresh.id)
|
||||
if(existingFresh) selected.value=existingFresh
|
||||
else{
|
||||
const kind=fresh.kind==='clip'?'clip':'still'
|
||||
selected.value={...fresh,kind,folderId:form.folderId,studio2:fresh.studio2}
|
||||
// Merge into catalog above; only auto-select when the user has not pinned a pick.
|
||||
if(!selectionPinned.value){
|
||||
const existingFresh=family.value.find(a=>a.id===fresh.id)
|
||||
if(existingFresh) selected.value=existingFresh
|
||||
else{
|
||||
const kind=fresh.kind==='clip'?'clip':'still'
|
||||
selected.value={...fresh,kind,folderId:form.folderId,studio2:fresh.studio2}
|
||||
}
|
||||
}
|
||||
}else if(selected.value){
|
||||
const next=reconcileSelection(selected.value,family.value)
|
||||
if(form.mode!=='music' && next?.kind==='track'){
|
||||
const nonTrack=family.value.find((a:any)=>a.kind!=='track') || null
|
||||
if(nonTrack && !selectionPinned.value) selected.value=nonTrack
|
||||
else if(next?.id===selected.value.id) selected.value=next
|
||||
}else if(next) selected.value=next
|
||||
}else{
|
||||
let next=reconcileSelection(selected.value,family.value)
|
||||
if(form.mode!=='music' && next?.kind==='track') next=family.value.find((a:any)=>a.kind!=='track') || null
|
||||
selected.value=next
|
||||
selected.value=reconcileSelection(null,family.value)
|
||||
}
|
||||
const newestTrack=(library.value.tracks||[]).filter((t:any)=>t.folderId===form.folderId).sort((a:any,b:any)=>(b.createdAt||0)-(a.createdAt||0))[0]
|
||||
if(form.mode==='music' && newestTrack && !seenOutputs.has(newestTrack.id)){
|
||||
@@ -1528,10 +1591,12 @@ async function queue(){
|
||||
.filter((p:any)=>p?.stillId)
|
||||
.map((p:any)=>({stillId:String(p.stillId),role:String(p.role||'change')}))
|
||||
const promptAction=String(form.promptSections?.action||'')
|
||||
// Image generate: split the prompt box on blank lines (parseBatch). Video/edit keep batchText.
|
||||
const imageParts=(!video.value && form.mode==='generate') ? parseBatch(promptAction) : null
|
||||
// Image generate + Batch on: double blank line only. Off: whole box is one prompt.
|
||||
const imageParts=(!video.value && form.mode==='generate' && batchMode.value)
|
||||
? parseBatchDoubleBlank(promptAction)
|
||||
: null
|
||||
const firstAction=imageParts?.length ? imageParts[0] : promptAction
|
||||
const imageBatch=imageParts?.length ? imageParts.slice(1) : []
|
||||
const imageBatch=imageParts && imageParts.length > 1 ? imageParts.slice(1) : []
|
||||
const promptSections=video.value
|
||||
?{
|
||||
action:promptAction,
|
||||
@@ -1600,6 +1665,7 @@ async function queue(){
|
||||
|
||||
syncPhotosBindings()
|
||||
if(form.settings.seedMode!=='locked')form.settings.seed=randomSeed()
|
||||
selectionPinned.value=false
|
||||
const body=applyPhotosToRequest({
|
||||
mode:optimisticRequest.mode==='edit' && parseBatch(batchText.value).length?'iterate':optimisticRequest.mode,
|
||||
engine:optimisticRequest.engine,
|
||||
@@ -1686,7 +1752,7 @@ async function fetchHealth(){
|
||||
watch(()=>[
|
||||
form.mode,form.engine,form.folderId,form.enhancePrompt,form.turbo,form.lockFace,form.lockOutfit,
|
||||
form.promptSections,form.photos,form.imageStyles,form.settings,
|
||||
eyeOpen.value,hideInputs.value,
|
||||
eyeOpen.value,hideInputs.value,batchMode.value,
|
||||
music.tags,music.lyrics,music.duration,music.seed,music.name
|
||||
],()=>saveBenchDraft(),{deep:true})
|
||||
let timer:ReturnType<typeof setTimeout>,clock:ReturnType<typeof setInterval>,disposed=false
|
||||
@@ -1696,6 +1762,7 @@ onMounted(()=>{
|
||||
loraMemory=createLoraMemory(xaigen,storage);loraMemory.sync(form);form.settings.seed=randomSeed()
|
||||
// First paint in ~2s: chrome + draft. Do not await library before the prompt works.
|
||||
restoreBenchDraft()
|
||||
if(['flux','krea','qwen21'].includes(form.engine)) rememberEngineSampling(form.engine)
|
||||
if(!form.folderId && library.value.folders?.[0]?.id) form.folderId=library.value.folders[0].id
|
||||
loadRevealed(form.folderId)
|
||||
clock=setInterval(()=>{now.value=Date.now()},1000)
|
||||
|
||||
@@ -78,14 +78,14 @@
|
||||
/>
|
||||
</div>
|
||||
<img
|
||||
v-else-if="liveThumbs.has(entry.key) && thumbSrc(entry)"
|
||||
v-else-if="!previewHidden && liveThumbs.has(entry.key) && thumbSrc(entry)"
|
||||
:src="thumbSrc(entry)"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
draggable="false"
|
||||
>
|
||||
<span v-else class="lib-modal-fallback" />
|
||||
<span v-else class="lib-modal-fallback">{{ previewHidden ? 'Hidden' : '' }}</span>
|
||||
<span class="lib-modal-kind">
|
||||
{{ entry.kind === 'clip' ? 'Video' : entry.kind === 'track' ? 'Music' : 'Image' }}
|
||||
</span>
|
||||
|
||||
@@ -139,6 +139,7 @@
|
||||
:selected="ctx.selected"
|
||||
:folder-id="ctx.form?.folderId"
|
||||
:preview-hidden="!ctx.eyeOpen"
|
||||
:is-hidden="ctx.isHidden"
|
||||
@close="ctx.closeLibrary()"
|
||||
@select="ctx.selectOutput"
|
||||
@use-image="ctx.onLibraryUseImage"
|
||||
@@ -223,7 +224,17 @@
|
||||
<label class="m-label">Lyrics<textarea v-model="ctx.music.lyrics" rows="8" class="m-area" /></label>
|
||||
</template>
|
||||
<template v-else>
|
||||
<label class="m-label">Prompt<textarea v-model="ctx.form.promptSections.action" rows="5" class="m-area" placeholder="Describe the image or the shot." /></label>
|
||||
<label class="m-label">Prompt
|
||||
<textarea
|
||||
:value="ctx.form.promptSections.action"
|
||||
rows="5"
|
||||
class="m-area"
|
||||
placeholder="Describe the image or the shot."
|
||||
readonly
|
||||
@focus="openPromptEditor"
|
||||
@click="openPromptEditor"
|
||||
/>
|
||||
</label>
|
||||
<label v-if="ctx.form.engine==='qwen21' && (ctx.form.mode==='generate' || ctx.form.mode==='edit')" class="m-check"><input v-model="ctx.form.enhancePrompt" type="checkbox"> Enhance prompt</label>
|
||||
<label v-if="ctx.form.engine==='qwen21' && (ctx.form.mode==='generate' || ctx.form.mode==='edit')" class="m-check"><input v-model="ctx.form.turbo" type="checkbox"> Turbo</label>
|
||||
<p v-if="ctx.form.engine==='qwen21' && ctx.form.turbo" class="m-muted">6-step Viggle distill. CFG stays 1. Enhance prompt recommended.</p>
|
||||
@@ -355,6 +366,25 @@
|
||||
<button v-if="ctx.canClearFinished" type="button" class="m-link" @click="ctx.clearFinished">Clear finished</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div
|
||||
v-if="promptEditorOpen"
|
||||
class="m-prompt-editor"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Edit prompt"
|
||||
>
|
||||
<header class="m-prompt-editor-head">
|
||||
<strong>Prompt</strong>
|
||||
<button type="button" @click="closePromptEditor">Done</button>
|
||||
</header>
|
||||
<textarea
|
||||
ref="promptEditorEl"
|
||||
v-model="promptEditorDraft"
|
||||
class="m-prompt-editor-area"
|
||||
placeholder="Describe the image or the shot."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -364,12 +394,33 @@ export type Studio2MobileCtx = Record<string, any>
|
||||
const props = defineProps<{ ctx: Studio2MobileCtx }>()
|
||||
const sheet = ref('')
|
||||
const touchStart = ref<{ x: number; y: number } | null>(null)
|
||||
const promptEditorOpen = ref(false)
|
||||
const promptEditorDraft = ref('')
|
||||
const promptEditorEl = ref<HTMLTextAreaElement | null>(null)
|
||||
|
||||
function openPromptEditor() {
|
||||
promptEditorDraft.value = String(props.ctx.form?.promptSections?.action || '')
|
||||
promptEditorOpen.value = true
|
||||
nextTick(() => {
|
||||
promptEditorEl.value?.focus()
|
||||
const el = promptEditorEl.value
|
||||
if (el) {
|
||||
const n = el.value.length
|
||||
el.setSelectionRange(n, n)
|
||||
}
|
||||
})
|
||||
}
|
||||
function closePromptEditor() {
|
||||
if (props.ctx.form?.promptSections) props.ctx.form.promptSections.action = promptEditorDraft.value
|
||||
promptEditorOpen.value = false
|
||||
}
|
||||
|
||||
function toggleSheet(id: string) {
|
||||
sheet.value = sheet.value === id ? '' : id
|
||||
}
|
||||
function closeMenus() {
|
||||
sheet.value = ''
|
||||
if (promptEditorOpen.value) closePromptEditor()
|
||||
}
|
||||
function runAction(kind: 'use-image' | 'use-prompt' | 'copy' | 'copy-raw' | 'load-input' | 'load-input-raw' | 'delete') {
|
||||
sheet.value = ''
|
||||
@@ -726,4 +777,41 @@ watch(() => props.ctx.form.mode, () => { sheet.value = '' })
|
||||
.m-job-progress { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
|
||||
.m-job-progress-bar { flex: 1; height: 4px; accent-color: var(--amber); }
|
||||
.m-link { border: 0; background: transparent; color: var(--amber); text-align: left; }
|
||||
.m-prompt-editor {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 90;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #0e0e10;
|
||||
padding: env(safe-area-inset-top, 0) env(safe-area-inset-right, 0) env(safe-area-inset-bottom, 0) env(safe-area-inset-left, 0);
|
||||
}
|
||||
.m-prompt-editor-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
.m-prompt-editor-head button {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--amber);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.m-prompt-editor-area {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
border: 0;
|
||||
resize: none;
|
||||
background: #0e0e10;
|
||||
color: #f3f1ea;
|
||||
font: inherit;
|
||||
font-size: 17px;
|
||||
line-height: 1.45;
|
||||
outline: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user