mirror of
https://github.com/Boof2015/prism.git
synced 2026-08-12 05:10:51 +02:00
improve ui/ux further
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import type { ScopeKind } from '../types/scope'
|
||||
|
||||
const DEFAULT_COLLAPSED_SCOPE_WEIGHT = 1
|
||||
|
||||
function usesCollapsedDefaultWeight(scope: ScopeKind): boolean {
|
||||
return scope === 'spectrogram'
|
||||
|| scope === 'vumeter'
|
||||
|| scope === 'lufsmeter'
|
||||
|| scope === 'waveform'
|
||||
}
|
||||
|
||||
export function buildAnalyzerGridTemplateColumns(
|
||||
visibleScopes: ScopeKind[],
|
||||
widthWeights: Partial<Record<ScopeKind, number>>
|
||||
): string {
|
||||
if (visibleScopes.length === 0) return ''
|
||||
|
||||
return visibleScopes.map((scope) => {
|
||||
const weight = widthWeights[scope] ?? 1
|
||||
|
||||
if (scope === 'vectorscope') {
|
||||
if (weight <= 0) {
|
||||
return 'minmax(96px, clamp(96px, 18vw, calc(var(--analyzer-height, 240px) - 8px)))'
|
||||
}
|
||||
return `minmax(clamp(96px, 18vw, calc(var(--analyzer-height, 240px) - 8px)), ${weight}fr)`
|
||||
}
|
||||
|
||||
if (usesCollapsedDefaultWeight(scope) && weight <= 0) {
|
||||
return `minmax(0, ${DEFAULT_COLLAPSED_SCOPE_WEIGHT}fr)`
|
||||
}
|
||||
|
||||
return `minmax(0, ${weight}fr)`
|
||||
}).join(' ')
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import { Waveform } from '../visualizers/Waveform'
|
||||
interface ScopeModuleProps {
|
||||
scopeKind: ScopeKind
|
||||
lineColor?: string
|
||||
widthWeight?: number
|
||||
}
|
||||
|
||||
interface Visualizer {
|
||||
@@ -98,7 +97,7 @@ function createVisualizer(scopeKind: ScopeKind, canvas: HTMLCanvasElement, mySet
|
||||
}
|
||||
}
|
||||
|
||||
export default function ScopeModule({ scopeKind, lineColor = '#38bdf8', widthWeight = 1 }: ScopeModuleProps): JSX.Element {
|
||||
export default function ScopeModule({ scopeKind, lineColor = '#38bdf8' }: ScopeModuleProps): JSX.Element {
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
const visualizerRef = useRef<Visualizer | null>(null)
|
||||
@@ -167,7 +166,6 @@ export default function ScopeModule({ scopeKind, lineColor = '#38bdf8', widthWei
|
||||
<div
|
||||
ref={containerRef}
|
||||
style={{
|
||||
flex: widthWeight,
|
||||
minWidth: 0,
|
||||
height: '100%',
|
||||
position: 'relative',
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useEffect, useRef, type CSSProperties, type JSX, type ReactNode } from 'react'
|
||||
import { useEffect, useMemo, useRef, type CSSProperties, type JSX, type ReactNode } from 'react'
|
||||
import { useAudioStore } from '../stores/audioStore'
|
||||
import { useSettingsStore, type ScopeSettings } from '../stores/settingsStore'
|
||||
import { useThemeStore, PRESETS, PRESET_IDS } from '../stores/themeStore'
|
||||
import type { ScopeKind } from '../../types/scope'
|
||||
import { buildAnalyzerGridTemplateColumns } from '../analyzerLayout'
|
||||
|
||||
const SCOPE_LABELS: Record<ScopeKind, string> = {
|
||||
spectrum: 'Spectrum',
|
||||
@@ -151,7 +152,7 @@ function RangeControl({
|
||||
)
|
||||
}
|
||||
|
||||
function ScopeSettingsCard({
|
||||
function ScopeSettingsSection({
|
||||
kind,
|
||||
settings,
|
||||
onUpdate,
|
||||
@@ -163,13 +164,13 @@ function ScopeSettingsCard({
|
||||
const scopeSettings = settings[kind]
|
||||
|
||||
return (
|
||||
<section className="settings-card">
|
||||
<div className="settings-card__header">
|
||||
<div className="settings-card__title">{SCOPE_LABELS[kind]}</div>
|
||||
<div className="settings-card__summary">{scopeSummary(kind, scopeSettings)}</div>
|
||||
<section className="settings-scope-section">
|
||||
<div className="settings-scope-section__header">
|
||||
<div className="settings-scope-section__title">{SCOPE_LABELS[kind]}</div>
|
||||
<div className="settings-scope-section__summary">{scopeSummary(kind, scopeSettings)}</div>
|
||||
</div>
|
||||
|
||||
<div className="settings-card__controls">
|
||||
<div className="settings-scope-section__controls">
|
||||
{kind === 'spectrum' && (() => {
|
||||
const current = scopeSettings as ScopeSettings['spectrum']
|
||||
return (
|
||||
@@ -487,11 +488,16 @@ export default function SettingsPanel({ onClose, onHeightChange }: SettingsPanel
|
||||
setCaptureMode,
|
||||
startCapture,
|
||||
} = useAudioStore()
|
||||
const { scopeSettings, updateScopeSettings, hiddenScopes, scopeOrder } = useSettingsStore()
|
||||
const { scopeSettings, updateScopeSettings, hiddenScopes, scopeOrder, widthWeights } = useSettingsStore()
|
||||
const { presetId, accent, setPreset, setCustomAccent, customAccent } = useThemeStore()
|
||||
const panelRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
const visibleScopes = scopeOrder.filter((kind) => !hiddenScopes.has(kind))
|
||||
const scopeTrackStyle = useMemo(() => {
|
||||
const gridTemplateColumns = buildAnalyzerGridTemplateColumns(visibleScopes, widthWeights)
|
||||
if (!gridTemplateColumns) return undefined
|
||||
return { gridTemplateColumns } as CSSProperties
|
||||
}, [visibleScopes, widthWeights])
|
||||
|
||||
useEffect(() => {
|
||||
void refreshDevices()
|
||||
@@ -542,8 +548,8 @@ export default function SettingsPanel({ onClose, onHeightChange }: SettingsPanel
|
||||
|
||||
return (
|
||||
<div className="settings-panel" ref={panelRef}>
|
||||
<div className="settings-panel__utility">
|
||||
<section className="settings-utility-card">
|
||||
<div className="settings-panel__utility-row">
|
||||
<section className="settings-utility-section settings-utility-section--source">
|
||||
<div className="settings-section-title">Audio Source</div>
|
||||
|
||||
<label className="settings-control settings-control--stack">
|
||||
@@ -576,7 +582,7 @@ export default function SettingsPanel({ onClose, onHeightChange }: SettingsPanel
|
||||
) : null}
|
||||
</section>
|
||||
|
||||
<section className="settings-utility-card">
|
||||
<section className="settings-utility-section settings-utility-section--theme">
|
||||
<div className="settings-section-title">Theme</div>
|
||||
|
||||
<div className="settings-theme-swatches">
|
||||
@@ -616,11 +622,19 @@ export default function SettingsPanel({ onClose, onHeightChange }: SettingsPanel
|
||||
</div>
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="settings-panel__close"
|
||||
onClick={onClose}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="settings-panel__scopes">
|
||||
<div className="settings-panel__scope-track" style={scopeTrackStyle}>
|
||||
{visibleScopes.map((kind) => (
|
||||
<ScopeSettingsCard
|
||||
<ScopeSettingsSection
|
||||
key={kind}
|
||||
kind={kind}
|
||||
settings={scopeSettings}
|
||||
@@ -628,14 +642,6 @@ export default function SettingsPanel({ onClose, onHeightChange }: SettingsPanel
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="settings-panel__close"
|
||||
onClick={onClose}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,30 +1,132 @@
|
||||
import { Fragment, useCallback, useRef, type JSX } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, type JSX } from 'react'
|
||||
import { useSettingsStore } from '../stores/settingsStore'
|
||||
import { useThemeStore } from '../stores/themeStore'
|
||||
import type { ScopeKind } from '../../types/scope'
|
||||
import ScopeModule from './ScopeModule'
|
||||
import { buildAnalyzerGridTemplateColumns } from '../analyzerLayout'
|
||||
|
||||
function ResizeHandle({ leftKind, rightKind }: { leftKind: ScopeKind; rightKind: ScopeKind }): JSX.Element {
|
||||
export default function Strip(): JSX.Element {
|
||||
const scopeOrder = useSettingsStore((s) => s.scopeOrder)
|
||||
const hiddenScopes = useSettingsStore((s) => s.hiddenScopes)
|
||||
const widthWeights = useSettingsStore((s) => s.widthWeights)
|
||||
const setScopeWidthWeight = useSettingsStore((s) => s.setScopeWidthWeight)
|
||||
const handleRef = useRef<HTMLDivElement>(null)
|
||||
const accent = useThemeStore((s) => s.accent)
|
||||
const stripRef = useRef<HTMLDivElement>(null)
|
||||
const gridRef = useRef<HTMLDivElement>(null)
|
||||
const scopeRefs = useRef<Partial<Record<ScopeKind, HTMLDivElement | null>>>({})
|
||||
const [handleOffsets, setHandleOffsets] = useState<number[]>([])
|
||||
const visibleScopes = scopeOrder.filter((k) => !hiddenScopes.has(k))
|
||||
const gridTemplateColumns = useMemo(() => {
|
||||
return buildAnalyzerGridTemplateColumns(visibleScopes, widthWeights)
|
||||
}, [visibleScopes, widthWeights])
|
||||
const gridStyle = useMemo(() => {
|
||||
if (!gridTemplateColumns) return undefined
|
||||
return { gridTemplateColumns } as CSSProperties
|
||||
}, [gridTemplateColumns])
|
||||
|
||||
const onMouseDown = useCallback((e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
const startX = e.clientX
|
||||
const container = handleRef.current?.parentElement
|
||||
if (!container) return
|
||||
const updateHandleOffsets = useCallback((): void => {
|
||||
if (visibleScopes.length < 2) {
|
||||
setHandleOffsets([])
|
||||
return
|
||||
}
|
||||
|
||||
const totalWidth = container.getBoundingClientRect().width
|
||||
const { widthWeights } = useSettingsStore.getState()
|
||||
const startLeftWeight = widthWeights[leftKind] ?? 1
|
||||
const startRightWeight = widthWeights[rightKind] ?? 1
|
||||
const nextOffsets: number[] = []
|
||||
for (let index = 0; index < visibleScopes.length - 1; index += 1) {
|
||||
const leftElement = scopeRefs.current[visibleScopes[index]]
|
||||
if (!leftElement) continue
|
||||
nextOffsets.push(leftElement.offsetLeft + leftElement.offsetWidth)
|
||||
}
|
||||
|
||||
setHandleOffsets(nextOffsets)
|
||||
}, [visibleScopes])
|
||||
|
||||
useEffect(() => {
|
||||
const strip = stripRef.current
|
||||
if (!strip) return
|
||||
|
||||
const setAnalyzerHeight = (): void => {
|
||||
const nextHeight = Math.max(0, Math.ceil(strip.getBoundingClientRect().height))
|
||||
document.documentElement.style.setProperty('--analyzer-height', `${nextHeight}px`)
|
||||
}
|
||||
|
||||
setAnalyzerHeight()
|
||||
|
||||
const observer = typeof ResizeObserver === 'undefined'
|
||||
? null
|
||||
: new ResizeObserver(() => setAnalyzerHeight())
|
||||
|
||||
observer?.observe(strip)
|
||||
window.addEventListener('resize', setAnalyzerHeight)
|
||||
|
||||
return () => {
|
||||
observer?.disconnect()
|
||||
window.removeEventListener('resize', setAnalyzerHeight)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
updateHandleOffsets()
|
||||
|
||||
const observer = typeof ResizeObserver === 'undefined'
|
||||
? null
|
||||
: new ResizeObserver(() => updateHandleOffsets())
|
||||
|
||||
if (gridRef.current) {
|
||||
observer?.observe(gridRef.current)
|
||||
}
|
||||
|
||||
for (const scope of visibleScopes) {
|
||||
const element = scopeRefs.current[scope]
|
||||
if (element) {
|
||||
observer?.observe(element)
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('resize', updateHandleOffsets)
|
||||
|
||||
return () => {
|
||||
observer?.disconnect()
|
||||
window.removeEventListener('resize', updateHandleOffsets)
|
||||
}
|
||||
}, [gridTemplateColumns, updateHandleOffsets, visibleScopes])
|
||||
|
||||
const startResizeDrag = useCallback((handleIndex: number, event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const leftKind = visibleScopes[handleIndex]
|
||||
const rightKind = visibleScopes[handleIndex + 1]
|
||||
if (!leftKind || !rightKind) return
|
||||
|
||||
const leftElement = scopeRefs.current[leftKind]
|
||||
const rightElement = scopeRefs.current[rightKind]
|
||||
if (!leftElement || !rightElement) return
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
const startX = event.clientX
|
||||
const leftWidth = leftElement.getBoundingClientRect().width
|
||||
const rightWidth = rightElement.getBoundingClientRect().width
|
||||
const pairWidth = Math.max(1, leftWidth + rightWidth)
|
||||
const { widthWeights: currentWeights } = useSettingsStore.getState()
|
||||
const startLeftWeight = currentWeights[leftKind] ?? 1
|
||||
const startRightWeight = currentWeights[rightKind] ?? 1
|
||||
const totalWeight = startLeftWeight + startRightWeight
|
||||
const minWeight = 0.15
|
||||
|
||||
const onMouseMove = (ev: MouseEvent): void => {
|
||||
const delta = ev.clientX - startX
|
||||
const ratio = delta / totalWidth * totalWeight * 2
|
||||
const newLeft = Math.max(0.15, startLeftWeight + ratio)
|
||||
const newRight = Math.max(0.15, startRightWeight - ratio)
|
||||
const weightDelta = (delta / pairWidth) * totalWeight
|
||||
let newLeft = startLeftWeight + weightDelta
|
||||
let newRight = startRightWeight - weightDelta
|
||||
|
||||
if (newLeft < minWeight) {
|
||||
newRight -= minWeight - newLeft
|
||||
newLeft = minWeight
|
||||
}
|
||||
|
||||
if (newRight < minWeight) {
|
||||
newLeft -= minWeight - newRight
|
||||
newRight = minWeight
|
||||
}
|
||||
|
||||
setScopeWidthWeight(leftKind, newLeft)
|
||||
setScopeWidthWeight(rightKind, newRight)
|
||||
}
|
||||
@@ -40,36 +142,38 @@ function ResizeHandle({ leftKind, rightKind }: { leftKind: ScopeKind; rightKind:
|
||||
document.body.style.userSelect = 'none'
|
||||
document.addEventListener('mousemove', onMouseMove)
|
||||
document.addEventListener('mouseup', onMouseUp)
|
||||
}, [leftKind, rightKind, setScopeWidthWeight])
|
||||
}, [setScopeWidthWeight, visibleScopes])
|
||||
|
||||
return (
|
||||
<div ref={handleRef} onMouseDown={onMouseDown} className="scope-strip__handle">
|
||||
<div className="scope-strip__handle-line" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div ref={stripRef} className="scope-strip">
|
||||
<div ref={gridRef} className="scope-strip__grid" style={gridStyle}>
|
||||
{visibleScopes.map((kind) => (
|
||||
<div
|
||||
key={kind}
|
||||
ref={(element) => {
|
||||
scopeRefs.current[kind] = element
|
||||
}}
|
||||
className="scope-strip__cell"
|
||||
>
|
||||
<ScopeModule
|
||||
scopeKind={kind}
|
||||
lineColor={accent}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
export default function Strip(): JSX.Element {
|
||||
const scopeOrder = useSettingsStore((s) => s.scopeOrder)
|
||||
const hiddenScopes = useSettingsStore((s) => s.hiddenScopes)
|
||||
const widthWeights = useSettingsStore((s) => s.widthWeights)
|
||||
const accent = useThemeStore((s) => s.accent)
|
||||
|
||||
const visibleScopes = scopeOrder.filter((k) => !hiddenScopes.has(k))
|
||||
|
||||
return (
|
||||
<div className="scope-strip">
|
||||
{visibleScopes.map((kind, i) => (
|
||||
<Fragment key={kind}>
|
||||
{i > 0 && (
|
||||
<ResizeHandle leftKind={visibleScopes[i - 1]} rightKind={kind} />
|
||||
)}
|
||||
<ScopeModule
|
||||
scopeKind={kind}
|
||||
lineColor={accent}
|
||||
widthWeight={widthWeights[kind] ?? 1}
|
||||
/>
|
||||
</Fragment>
|
||||
{visibleScopes.length > 1 && handleOffsets.map((offset, index) => (
|
||||
<button
|
||||
key={`${visibleScopes[index]}:${visibleScopes[index + 1]}`}
|
||||
type="button"
|
||||
className="scope-strip__resize-handle"
|
||||
style={{ left: `${offset}px` }}
|
||||
onMouseDown={(event) => startResizeDrag(index, event)}
|
||||
aria-label={`Resize between ${visibleScopes[index]} and ${visibleScopes[index + 1]}`}
|
||||
>
|
||||
<span className="scope-strip__resize-handle-grip" aria-hidden="true" />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -121,8 +121,8 @@ select {
|
||||
.toolbar__chip,
|
||||
.toolbar__icon-button,
|
||||
.settings-section-title,
|
||||
.settings-card__title,
|
||||
.settings-card__summary,
|
||||
.settings-scope-section__title,
|
||||
.settings-scope-section__summary,
|
||||
.settings-control__label,
|
||||
.settings-chip,
|
||||
.settings-status-pill,
|
||||
@@ -221,24 +221,42 @@ select {
|
||||
}
|
||||
|
||||
.scope-strip {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--bg-primary);
|
||||
}
|
||||
|
||||
.scope-strip__handle {
|
||||
width: 6px;
|
||||
flex-shrink: 0;
|
||||
.scope-strip__grid {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.scope-strip__cell {
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.scope-strip__resize-handle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 12px;
|
||||
transform: translateX(-50%);
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
cursor: col-resize;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: stretch;
|
||||
justify-content: center;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.scope-strip__handle-line {
|
||||
.scope-strip__resize-handle-grip {
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
background: linear-gradient(180deg, transparent, rgba(255, 255, 255, 0.12), transparent);
|
||||
@@ -248,75 +266,79 @@ select {
|
||||
background: linear-gradient(180deg, rgba(6, 8, 11, 0.98), rgba(4, 5, 7, 0.98));
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
padding-bottom: 12px;
|
||||
padding: 12px 0 14px;
|
||||
}
|
||||
|
||||
.settings-panel__utility {
|
||||
width: 224px;
|
||||
padding: 12px;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.06);
|
||||
.settings-panel__utility-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
align-items: flex-start;
|
||||
gap: 14px 18px;
|
||||
flex-wrap: wrap;
|
||||
padding: 0 14px 10px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.settings-utility-card,
|
||||
.settings-card {
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--panel-outline);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.018), rgba(255, 255, 255, 0.008)),
|
||||
var(--panel-surface-soft);
|
||||
box-shadow: var(--panel-shadow);
|
||||
.settings-utility-section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
gap: 10px 14px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.settings-utility-card {
|
||||
padding: 12px;
|
||||
.settings-utility-section--source {
|
||||
flex: 1 1 360px;
|
||||
}
|
||||
|
||||
.settings-utility-section--theme {
|
||||
flex: 1 1 300px;
|
||||
}
|
||||
|
||||
.settings-panel__scope-track {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
gap: 0;
|
||||
padding: 0 0 2px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.settings-section-title {
|
||||
width: 100%;
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.settings-panel__scopes {
|
||||
flex: 1;
|
||||
.settings-scope-section {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: minmax(240px, 1fr);
|
||||
gap: 12px;
|
||||
padding: 12px 12px 36px;
|
||||
overflow-x: auto;
|
||||
overflow-y: visible;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.settings-card {
|
||||
min-width: 240px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 0 16px;
|
||||
border-left: 1px solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.settings-card__header {
|
||||
.settings-scope-section:first-child {
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.settings-scope-section__header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.settings-card__title {
|
||||
.settings-scope-section__title {
|
||||
color: var(--accent);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.settings-card__summary {
|
||||
.settings-scope-section__summary {
|
||||
color: var(--text-tertiary);
|
||||
font-size: 9px;
|
||||
letter-spacing: 0.08em;
|
||||
@@ -325,10 +347,10 @@ select {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.settings-card__controls {
|
||||
.settings-scope-section__controls {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px 12px;
|
||||
grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
|
||||
gap: 10px 14px;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
@@ -368,17 +390,19 @@ select {
|
||||
padding: 0 12px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: var(--panel-surface);
|
||||
background: rgba(8, 12, 18, 0.72);
|
||||
color: rgba(255, 255, 255, 0.86);
|
||||
font-size: 11px;
|
||||
outline: none;
|
||||
backdrop-filter: blur(14px) saturate(1.04);
|
||||
-webkit-backdrop-filter: blur(14px) saturate(1.04);
|
||||
transition: border-color 140ms ease, background-color 140ms ease;
|
||||
}
|
||||
|
||||
.settings-control__select:hover,
|
||||
.settings-control__select:focus {
|
||||
border-color: rgba(255, 255, 255, 0.16);
|
||||
background: rgba(10, 14, 20, 0.98);
|
||||
background: rgba(12, 18, 26, 0.82);
|
||||
}
|
||||
|
||||
.settings-control__range {
|
||||
@@ -432,9 +456,11 @@ select {
|
||||
padding: 0 11px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
background: rgba(8, 12, 18, 0.72);
|
||||
color: var(--text-secondary);
|
||||
font-size: 10px;
|
||||
backdrop-filter: blur(14px) saturate(1.04);
|
||||
-webkit-backdrop-filter: blur(14px) saturate(1.04);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color 140ms ease,
|
||||
@@ -463,9 +489,11 @@ select {
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
background: rgba(8, 12, 18, 0.72);
|
||||
color: var(--text-secondary);
|
||||
font-size: 10px;
|
||||
backdrop-filter: blur(14px) saturate(1.04);
|
||||
-webkit-backdrop-filter: blur(14px) saturate(1.04);
|
||||
}
|
||||
|
||||
.settings-status-pill__dot {
|
||||
@@ -496,7 +524,6 @@ select {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.settings-swatch {
|
||||
@@ -520,31 +547,31 @@ select {
|
||||
.settings-accent-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.settings-accent-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
width: 92px;
|
||||
height: 32px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: var(--panel-surface);
|
||||
background: rgba(8, 12, 18, 0.72);
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.settings-panel__close {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
bottom: 10px;
|
||||
margin-left: auto;
|
||||
min-height: 28px;
|
||||
padding: 0 12px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
background: rgba(8, 12, 18, 0.72);
|
||||
color: var(--text-tertiary);
|
||||
font-size: 9px;
|
||||
backdrop-filter: blur(14px) saturate(1.04);
|
||||
-webkit-backdrop-filter: blur(14px) saturate(1.04);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color 140ms ease,
|
||||
|
||||
Reference in New Issue
Block a user