@@ -2571,72 +2572,6 @@ function CameraConfig(props: { scrollRef: HTMLDivElement }) {
);
}
-function CornerStyleSelect(props: {
- label?: string;
- value: CornerRoundingType;
- onChange: (value: CornerRoundingType) => void;
-}) {
- return (
-
-
- {(label) => (
-
- {label()}
-
- )}
-
-
- options={CORNER_STYLE_OPTIONS}
- optionValue="value"
- optionTextValue="name"
- value={CORNER_STYLE_OPTIONS.find(
- (option) => option.value === props.value,
- )}
- onChange={(option) => option && props.onChange(option.value)}
- disallowEmptySelection
- itemComponent={(itemProps) => (
-
- )}
- >
-
- class="flex-1 text-sm text-left truncate text-[--gray-500] font-normal">
- {(state) => {state.selectedOption().name}}
-
-
- as={(iconProps) => (
-
- )}
- />
-
-
-
- as={KSelect.Content}
- class={cx(topSlideAnimateClasses, "z-50")}
- >
-
- class="overflow-y-auto max-h-32"
- as={KSelect.Listbox}
- />
-
-
-
-
- );
-}
-
function HexColorInput(props: {
value: string;
onChange: (value: string) => void;
@@ -3285,8 +3220,7 @@ function ZoomSegmentPreview(props: {
createEffect(() => {
// TODO: make this not hardcoded
const path = convertFileSrc(
- `${editorInstance.path}/content/segments/segment-${
- clipSegment()?.recordingSegment ?? 0
+ `${editorInstance.path}/content/segments/segment-${clipSegment()?.recordingSegment ?? 0
}/display.mp4`,
);
video.src = path;
@@ -3489,8 +3423,7 @@ function ZoomSegmentConfig(props: {
createEffect(() => {
const path = convertFileSrc(
// TODO: this shouldn't be so hardcoded
- `${
- editorInstance.path
+ `${editorInstance.path
}/content/segments/segment-${segmentIndex()}/display.mp4`,
);
video.src = path;
@@ -3620,7 +3553,7 @@ function ZoomSegmentConfig(props: {
x: Math.max(
Math.min(
(moveEvent.clientX - bounds.left) /
- bounds.width,
+ bounds.width,
1,
),
0,
@@ -3628,7 +3561,7 @@ function ZoomSegmentConfig(props: {
y: Math.max(
Math.min(
(moveEvent.clientY - bounds.top) /
- bounds.height,
+ bounds.height,
1,
),
0,
diff --git a/apps/desktop/src/routes/editor/context.ts b/apps/desktop/src/routes/editor/context.ts
index 24987bd5f1..792270e45c 100644
--- a/apps/desktop/src/routes/editor/context.ts
+++ b/apps/desktop/src/routes/editor/context.ts
@@ -132,10 +132,6 @@ export type CustomDomainResponse = {
domain_verified: boolean | null;
};
-export type CornerRoundingType = "rounded" | "squircle";
-
-type WithCornerStyle
= T & { roundingType: CornerRoundingType };
-
type EditorTimelineConfiguration = Omit<
TimelineConfiguration,
"sceneSegments" | "maskSegments"
@@ -149,25 +145,12 @@ export type EditorProjectConfiguration = Omit<
ProjectConfiguration,
"background" | "camera" | "timeline"
> & {
- background: WithCornerStyle;
- camera: WithCornerStyle;
+ background: ProjectConfiguration["background"];
+ camera: ProjectConfiguration["camera"];
timeline?: EditorTimelineConfiguration | null;
hiddenTextSegments?: number[];
};
-function withCornerDefaults<
- T extends {
- roundingType?: CornerRoundingType;
- rounding_type?: CornerRoundingType;
- },
->(value: T): T & { roundingType: CornerRoundingType } {
- const roundingType = value.roundingType ?? value.rounding_type ?? "squircle";
- return {
- ...value,
- roundingType,
- };
-}
-
export function normalizeProject(
config: ProjectConfiguration,
): EditorProjectConfiguration {
@@ -209,8 +192,6 @@ export function normalizeProject(
...config,
keyboard,
timeline,
- background: withCornerDefaults(config.background),
- camera: withCornerDefaults(config.camera),
};
}
@@ -218,9 +199,6 @@ export function serializeProjectConfiguration(
project: EditorProjectConfiguration,
): ProjectConfiguration {
const { background, camera, ...rest } = project;
- const { roundingType: backgroundRoundingType, ...backgroundRest } =
- background;
- const { roundingType: cameraRoundingType, ...cameraRest } = camera;
const timeline = project.timeline
? {
@@ -235,14 +213,8 @@ export function serializeProjectConfiguration(
return {
...rest,
timeline: timeline as unknown as ProjectConfiguration["timeline"],
- background: {
- ...backgroundRest,
- roundingType: backgroundRoundingType,
- },
- camera: {
- ...cameraRest,
- roundingType: cameraRoundingType,
- },
+ background,
+ camera,
};
}
diff --git a/apps/desktop/src/routes/screenshot-editor/popovers/RoundingPopover.tsx b/apps/desktop/src/routes/screenshot-editor/popovers/RoundingPopover.tsx
index 85d0aec3a2..7bb1b9f3af 100644
--- a/apps/desktop/src/routes/screenshot-editor/popovers/RoundingPopover.tsx
+++ b/apps/desktop/src/routes/screenshot-editor/popovers/RoundingPopover.tsx
@@ -5,20 +5,7 @@ import { batch, Show, type ValidComponent } from "solid-js";
import IconCapChevronDown from "~icons/cap/chevron-down";
import IconCapCorners from "~icons/cap/corners";
import { useScreenshotEditorContext } from "../context";
-import {
- EditorButton,
- MenuItem,
- MenuItemList,
- PopperContent,
- Slider,
- topSlideAnimateClasses,
-} from "../ui";
-
-export type CornerRoundingType = "rounded" | "squircle";
-const CORNER_STYLE_OPTIONS = [
- { name: "Squircle", value: "squircle" },
- { name: "Rounded", value: "rounded" },
-] satisfies Array<{ name: string; value: CornerRoundingType }>;
+import { EditorButton, Slider } from "../ui";
function hasNoVisibleBackground(source: {
type: string;
@@ -84,90 +71,40 @@ export function RoundingPopover() {
-
Rounding
-
+
+
+
+ Rounding
+
+ setProject("background", "rounding", v[0])}
+ minValue={0}
+ maxValue={100}
+ step={0.1}
+ formatTooltip="%"
+ />
+
+
+
+
+ Rounding Smoothness
+
+
+ setProject("background", "roundingSmoothness", v[0])
+ }
+ minValue={0}
+ maxValue={1}
+ step={0.01}
+ formatTooltip={(value) => `${Math.round(value * 100)}%`}
+ />
+
-
setProject("background", "roundingType", v)}
- />
);
}
-
-function CornerStyleSelect(props: {
- label?: string;
- value: CornerRoundingType;
- onChange: (value: CornerRoundingType) => void;
-}) {
- return (
-
-
- {(label) => (
-
- {label()}
-
- )}
-
-
- options={CORNER_STYLE_OPTIONS}
- optionValue="value"
- optionTextValue="name"
- value={CORNER_STYLE_OPTIONS.find(
- (option) => option.value === props.value,
- )}
- onChange={(option) => option && props.onChange(option.value)}
- disallowEmptySelection
- itemComponent={(itemProps) => (
-
- )}
- >
-
- class="flex-1 text-sm text-left truncate text-[--gray-500] font-normal">
- {(state) => {state.selectedOption().name}}
-
-
- as={(iconProps) => (
-
- )}
- />
-
-
-
- as={KSelect.Content}
- class={cx(topSlideAnimateClasses, "z-50")}
- >
-
- class="overflow-y-auto max-h-32"
- as={KSelect.Listbox}
- />
-
-
-
-
- );
-}
diff --git a/apps/desktop/src/utils/tauri.ts b/apps/desktop/src/utils/tauri.ts
index 22b6d2acf8..245949316f 100644
--- a/apps/desktop/src/utils/tauri.ts
+++ b/apps/desktop/src/utils/tauri.ts
@@ -441,10 +441,10 @@ export type AuthSecret = { api_key: string } | { token: string; expires: number
export type AuthStore = { secret: AuthSecret; user_id: string | null; plan: Plan | null; organizations?: Organization[] }
export type BackgroundBlurConfig = { mode: BackgroundBlurMode }
export type BackgroundBlurMode = "off" | "light" | "heavy"
-export type BackgroundConfiguration = { source: BackgroundSource; blur: number; padding: number; rounding: number; roundingType: CornerStyle; inset: number; crop: Crop | null; shadow: number; advancedShadow: ShadowConfiguration | null; border: BorderConfiguration | null }
+export type BackgroundConfiguration = { source: BackgroundSource; blur: number; padding: number; rounding: number; roundingSmoothness?: number; inset: number; crop: Crop | null; shadow: number; advancedShadow: ShadowConfiguration | null; border: BorderConfiguration | null }
export type BackgroundSource = { type: "wallpaper"; path: string | null } | { type: "image"; path: string | null } | { type: "color"; value: [number, number, number]; alpha?: number } | { type: "gradient"; from: [number, number, number]; to: [number, number, number]; angle?: number; noise_intensity?: number | null; noise_scale?: number | null; animated?: boolean | null; animation_speed?: number | null }
export type BorderConfiguration = { enabled: boolean; width: number; color: [number, number, number]; opacity: number }
-export type Camera = { hide: boolean; mirror: boolean; position: CameraPosition; size: number; zoomSize: number | null; rounding: number; shadow: number; advancedShadow: ShadowConfiguration | null; shape: CameraShape; roundingType: CornerStyle; scaleDuringZoom?: number; backgroundBlur?: BackgroundBlurConfig }
+export type Camera = { hide: boolean; mirror: boolean; position: CameraPosition; size: number; zoomSize: number | null; rounding: number; shadow: number; advancedShadow: ShadowConfiguration | null; shape: CameraShape; roundingSmoothness?: number; scaleDuringZoom?: number; backgroundBlur?: BackgroundBlurConfig }
export type CameraDeviceSettings = { width: number | null; height: number | null; frameRate: number | null }
export type CameraFormatInfo = { width: number; height: number; frameRate: number }
export type CameraInfo = { device_id: string; model_id: ModelIDType | null; display_name: string }
@@ -469,7 +469,6 @@ export type ClickSpringConfig = { tension: number; mass: number; friction: numbe
export type ClipConfiguration = { index: number; offsets: ClipOffsets }
export type ClipOffsets = { camera?: number; mic?: number; system_audio?: number }
export type CommercialLicense = { licenseKey: string; expiryDate: number | null; refresh: number; activatedOn: number }
-export type CornerStyle = "squircle" | "rounded"
export type Crop = { position: XY; size: XY }
export type CurrentRecording = { target: CurrentRecordingTarget; mode: RecordingMode; status: RecordingStatus }
export type CurrentRecordingChanged = null
diff --git a/crates/project/src/configuration.rs b/crates/project/src/configuration.rs
index e6607e5c18..46e29a9c5d 100644
--- a/crates/project/src/configuration.rs
+++ b/crates/project/src/configuration.rs
@@ -60,6 +60,10 @@ fn default_alpha() -> u8 {
u8::MAX
}
+fn default_rounding_smoothness() -> f32 {
+ 0.6
+}
+
impl Default for BackgroundSource {
fn default() -> Self {
BackgroundSource::Color {
@@ -191,14 +195,6 @@ impl From<(T, T)> for XY {
}
}
-#[derive(Type, Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Default)]
-#[serde(rename_all = "camelCase")]
-pub enum CornerStyle {
- #[default]
- Squircle,
- Rounded,
-}
-
#[derive(Type, Serialize, Deserialize, Clone, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct Crop {
@@ -236,7 +232,8 @@ pub struct BackgroundConfiguration {
pub blur: f64,
pub padding: f64,
pub rounding: f64,
- pub rounding_type: CornerStyle,
+ #[serde(default = "default_rounding_smoothness")]
+ pub rounding_smoothness: f32,
pub inset: u32,
pub crop: Option,
pub shadow: f32,
@@ -262,12 +259,12 @@ impl Default for BackgroundConfiguration {
blur: 0.0,
padding: 0.0,
rounding: 0.0,
- rounding_type: CornerStyle::default(),
+ rounding_smoothness: 0.6,
inset: 0,
crop: None,
shadow: 73.6,
advanced_shadow: Some(ShadowConfiguration::default()),
- border: None, // Border is disabled by default for backwards compatibility
+ border: None,
}
}
}
@@ -339,8 +336,8 @@ pub struct Camera {
#[serde(alias = "advanced_shadow")]
pub advanced_shadow: Option,
pub shape: CameraShape,
- #[serde(alias = "rounding_type")]
- pub rounding_type: CornerStyle,
+ #[serde(default = "default_rounding_smoothness")]
+ pub rounding_smoothness: f32,
#[serde(default = "Camera::default_scale_during_zoom")]
pub scale_during_zoom: f32,
#[serde(default)]
@@ -385,7 +382,7 @@ impl Default for Camera {
blur: 10.5,
}),
shape: CameraShape::Square,
- rounding_type: CornerStyle::default(),
+ rounding_smoothness: 0.0,
scale_during_zoom: Self::default_scale_during_zoom(),
background_blur: BackgroundBlurConfig::default(),
}
diff --git a/crates/rendering/src/composite_frame.rs b/crates/rendering/src/composite_frame.rs
index ccdad6b540..fde6bf364f 100644
--- a/crates/rendering/src/composite_frame.rs
+++ b/crates/rendering/src/composite_frame.rs
@@ -1,6 +1,8 @@
use bytemuck::{Pod, Zeroable};
use wgpu::{include_wgsl, util::DeviceExt};
+use crate::smoothness_to_exponent;
+
pub struct CompositeVideoFramePipeline {
pub bind_group_layout: wgpu::BindGroupLayout,
pub render_pipeline: wgpu::RenderPipeline,
@@ -75,7 +77,7 @@ pub struct CompositeVideoFrameUniforms {
pub motion_blur_params: [f32; 4],
pub target_size: [f32; 2],
pub rounding_px: f32,
- pub rounding_type: f32,
+ pub corner_exponent: f32,
pub mirror_x: f32,
pub shadow: f32,
pub shadow_size: f32,
@@ -100,7 +102,7 @@ impl Default for CompositeVideoFrameUniforms {
motion_blur_params: Default::default(),
target_size: Default::default(),
rounding_px: Default::default(),
- rounding_type: 0.0,
+ corner_exponent: smoothness_to_exponent(0.6),
mirror_x: Default::default(),
shadow: Default::default(),
shadow_size: Default::default(),
diff --git a/crates/rendering/src/lib.rs b/crates/rendering/src/lib.rs
index a87a27e61f..34b83a44ca 100644
--- a/crates/rendering/src/lib.rs
+++ b/crates/rendering/src/lib.rs
@@ -1,7 +1,7 @@
use anyhow::Result;
use cap_project::{
- AspectRatio, CameraShape, CameraXPosition, CameraYPosition, ClipOffsets, CornerStyle, Crop,
- CursorEvents, MaskKind, ProjectConfiguration, RecordingMeta, StudioRecordingMeta, XY,
+ AspectRatio, CameraShape, CameraXPosition, CameraYPosition, ClipOffsets, Crop, CursorEvents,
+ MaskKind, ProjectConfiguration, RecordingMeta, StudioRecordingMeta, XY,
};
use composite_frame::CompositeVideoFrameUniforms;
use core::f64;
@@ -164,13 +164,6 @@ pub async fn probe_software_adapter() -> Option<(bool, String)> {
const STANDARD_CURSOR_HEIGHT: f32 = 75.0;
-fn rounding_type_value(style: CornerStyle) -> f32 {
- match style {
- CornerStyle::Rounded => 0.0,
- CornerStyle::Squircle => 1.0,
- }
-}
-
#[derive(Debug, Clone, Copy, Type)]
pub struct RenderOptions {
pub camera_size: Option>,
@@ -2602,9 +2595,11 @@ impl ProjectUniforms {
],
target_bounds: [start.x as f32, start.y as f32, end.x as f32, end.y as f32],
target_size: [target_size.x as f32, target_size.y as f32],
- rounding_px: (project.background.rounding / 100.0 * 0.5 * min_target_axis)
- as f32,
- rounding_type: rounding_type_value(project.background.rounding_type),
+ rounding_px: compute_adjusted_radius(
+ project.background.rounding as f32 / 100.0 * 0.5 * min_target_axis as f32,
+ project.background.rounding_smoothness,
+ ),
+ corner_exponent: smoothness_to_exponent(project.background.rounding_smoothness),
mirror_x: 0.0,
motion_blur_vector: descriptor.movement_vector_uv,
motion_blur_zoom_center: descriptor.zoom_center_uv,
@@ -2778,8 +2773,11 @@ impl ProjectUniforms {
target_bounds[2] - target_bounds[0],
target_bounds[3] - target_bounds[1],
],
- rounding_px: project.camera.rounding / 100.0 * 0.5 * size[0].min(size[1]),
- rounding_type: rounding_type_value(project.camera.rounding_type),
+ rounding_px: compute_adjusted_radius(
+ project.camera.rounding / 100.0 * 0.5 * size[0].min(size[1]),
+ project.camera.rounding_smoothness,
+ ),
+ corner_exponent: smoothness_to_exponent(project.camera.rounding_smoothness),
mirror_x: if project.camera.mirror { 1.0 } else { 0.0 },
motion_blur_vector: camera_descriptor.movement_vector_uv,
motion_blur_zoom_center: camera_descriptor.zoom_center_uv,
@@ -2875,7 +2873,7 @@ impl ProjectUniforms {
target_bounds[3] - target_bounds[1],
],
rounding_px: 0.0,
- rounding_type: rounding_type_value(project.camera.rounding_type),
+ corner_exponent: 0.0,
mirror_x: if project.camera.mirror { 1.0 } else { 0.0 },
motion_blur_vector: camera_only_descriptor.movement_vector_uv,
motion_blur_zoom_center: camera_only_descriptor.zoom_center_uv,
@@ -3986,6 +3984,26 @@ pub fn create_shader_render_pipeline(
})
}
+// Converts a smoothness value (0.0 to 1.0) to a super ellipse exponent.
+#[inline]
+pub(crate) fn smoothness_to_exponent(smoothness: f32) -> f32 {
+ 2.0 + smoothness * 6.0
+}
+
+// Adjusts radius to maintain visual consistency across different super ellipse exponents.
+fn compute_adjusted_radius(radius: f32, smoothness: f32) -> f32 {
+ if smoothness == 0.0 {
+ return radius;
+ }
+
+ let power = smoothness_to_exponent(smoothness);
+ let capsule_scale = 2.0f32.powf(0.5 - 1.0 / power);
+
+ // Visual normalization: compensate for super ellipses appearing smaller
+ let visual_compensation = 1.0 + smoothness * 0.8;
+ radius * capsule_scale * visual_compensation
+}
+
#[cfg(test)]
mod project_uniforms_tests {
use super::*;
diff --git a/crates/rendering/src/shaders/composite-video-frame.wgsl b/crates/rendering/src/shaders/composite-video-frame.wgsl
index 398a558626..a68b3b366c 100644
--- a/crates/rendering/src/shaders/composite-video-frame.wgsl
+++ b/crates/rendering/src/shaders/composite-video-frame.wgsl
@@ -8,7 +8,7 @@ struct Uniforms {
motion_blur_params: vec4,
target_size: vec2,
rounding_px: f32,
- rounding_type: f32,
+ corner_exponent: f32,
mirror_x: f32,
shadow: f32,
shadow_size: f32,
@@ -50,19 +50,20 @@ fn superellipse_norm(p: vec2, power: f32) -> f32 {
return pow(x + y, 1.0 / power);
}
-fn rounded_corner_norm(p: vec2, rounding_type: f32) -> f32 {
- if rounding_type < 0.5 {
+// Accept an exponent directly. If exponent <= 0 -> use circular corner (length).
+fn rounded_corner_norm(p: vec2, exponent: f32) -> f32 {
+ if exponent <= 0.0 {
return length(p);
}
-
- let power = 4.0;
+ // protect against pathological exponent values
+ let power = max(exponent, 1e-3);
return superellipse_norm(p, power);
}
-fn sdf_rounded_rect(p: vec2, b: vec2, r: f32, rounding_type: f32) -> f32 {
+fn sdf_rounded_rect(p: vec2, b: vec2, r: f32, exponent: f32) -> f32 {
let q = abs(p) - b + vec2(r);
let outside = max(q, vec2(0.0));
- let outside_norm = rounded_corner_norm(outside, rounding_type);
+ let outside_norm = rounded_corner_norm(outside, exponent);
return outside_norm + min(max(q.x, q.y), 0.0) - r;
}
@@ -71,8 +72,8 @@ fn fs_main(@builtin(position) frag_coord: vec4) -> @location(0) vec4 {
let p = frag_coord.xy;
let center = (uniforms.target_bounds.xy + uniforms.target_bounds.zw) * 0.5;
let size = (uniforms.target_bounds.zw - uniforms.target_bounds.xy) * 0.5;
-
- let dist = sdf_rounded_rect(p - center, size, uniforms.rounding_px, uniforms.rounding_type);
+
+ let dist = sdf_rounded_rect(p - center, size, uniforms.rounding_px, uniforms.corner_exponent);
let min_frame_size = min(size.x, size.y);
let shadow_enabled = uniforms.shadow > 0.0;
@@ -99,7 +100,7 @@ fn fs_main(@builtin(position) frag_coord: vec4) -> @location(0) vec4 {
shadow_enabled
);
- let shadow_dist = sdf_rounded_rect(p - center, size, uniforms.rounding_px, uniforms.rounding_type);
+ let shadow_dist = sdf_rounded_rect(p - center, size, uniforms.rounding_px, uniforms.corner_exponent);
// Apply blur and size to shadow
let shadow_strength_final = smoothstep(shadow_size + shadow_blur, -shadow_blur, abs(shadow_dist));
@@ -116,10 +117,10 @@ fn fs_main(@builtin(position) frag_coord: vec4) -> @location(0) vec4 {
p - center,
size + vec2(uniforms.border_width),
uniforms.rounding_px + uniforms.border_width,
- uniforms.rounding_type
+ uniforms.corner_exponent
);
let border_inner_dist =
- sdf_rounded_rect(p - center, size, uniforms.rounding_px, uniforms.rounding_type);
+ sdf_rounded_rect(p - center, size, uniforms.rounding_px, uniforms.corner_exponent);
if (border_outer_dist <= 0.0 && border_inner_dist > 0.0) {
let inner_alpha = smoothstep(-0.5, 0.5, border_inner_dist);
@@ -130,7 +131,7 @@ fn fs_main(@builtin(position) frag_coord: vec4) -> @location(0) vec4 {
return vec4(uniforms.border_color.xyz, border_alpha);
}
}
-
+
if target_uv.x < 0.0 || target_uv.x > 1.0 || target_uv.y < 0.0 || target_uv.y > 1.0 {
return shadow_color;
}
@@ -261,7 +262,7 @@ fn sample_texture(uv: vec2, crop_bounds_uv: vec4) -> vec4 {
fn apply_rounded_corners(current_color: vec4, target_uv: vec2) -> vec4 {
let centered_uv = (target_uv - vec2(0.5)) * uniforms.target_size;
let half_size = uniforms.target_size * 0.5;
- let distance = sdf_rounded_rect(centered_uv, half_size, uniforms.rounding_px, uniforms.rounding_type);
+ let distance = sdf_rounded_rect(centered_uv, half_size, uniforms.rounding_px, uniforms.corner_exponent);
let anti_alias_width = max(fwidth(distance), 0.5);
let coverage = clamp(1.0 - smoothstep(0.0, anti_alias_width, distance), 0.0, 1.0);
diff --git a/packages/ui-solid/src/auto-imports.d.ts b/packages/ui-solid/src/auto-imports.d.ts
index 6c6a4bad98..f21afc2e80 100644
--- a/packages/ui-solid/src/auto-imports.d.ts
+++ b/packages/ui-solid/src/auto-imports.d.ts
@@ -107,7 +107,9 @@ declare global {
const IconLucideSettings: typeof import('~icons/lucide/settings.jsx')['default']
const IconLucideSparkles: typeof import('~icons/lucide/sparkles.jsx')['default']
const IconLucideSquarePlay: typeof import('~icons/lucide/square-play.jsx')['default']
+ const IconLucideSquareRoundCorner: typeof import('~icons/lucide/square-round-corner.jsx')['default']
const IconLucideSubtitles: typeof import('~icons/lucide/subtitles.jsx')['default']
+ const IconLucideTimer: typeof import('~icons/lucide/timer.jsx')['default']
const IconLucideType: typeof import('~icons/lucide/type.jsx')['default']
const IconLucideUnplug: typeof import('~icons/lucide/unplug.jsx')['default']
const IconLucideVideo: typeof import('~icons/lucide/video.jsx')['default']