fix background color

This commit is contained in:
AnActualEmerald 2023-10-18 14:22:59 -04:00
parent 598b0fe277
commit 4b8c7536ea
Signed by: emerald
GPG Key ID: 420C9E1863CCB30F
15 changed files with 336 additions and 360 deletions

View File

@ -3,7 +3,7 @@ set export
alias d := debug
dev:
-cargo tauri dev
-yarn tauri dev
debug:
RUST_LOG=debug yarn tauri dev

View File

@ -25,20 +25,32 @@ pub fn monitor(
.build_input_stream(
&config.config(),
move |data: &[f32], _: &InputCallbackInfo| {
if data
let Some(new_level) = data
.iter()
.any(|e| (e.abs() * *sens.lock().unwrap()) >= *threshold.lock().unwrap())
.map(|v| {
let val = v.abs();
if val < 0.0001 {
0.0
} else {
val * *sens.lock().expect("Unable to lock mic sens mutex")
}
})
.reduce(|prev, curr| prev + curr)
.map(|v| v / data.len() as f32)
else {
return;
};
if new_level
> *threshold
.lock()
.expect("Unable to lock mic threshold mutex")
{
window.emit("mouth-open", "").unwrap();
} else {
window.emit("mouth-close", "").unwrap();
}
*level.lock().unwrap() = data
.iter()
.map(|e| e.abs() * *sens.lock().unwrap())
.max_by(|a, b| a.total_cmp(&b))
.unwrap();
*level.lock().unwrap() = new_level;
},
move |err| {
println!("Audio error: {:?}", err);
@ -55,7 +67,6 @@ pub fn monitor(
}
fn initialize() -> Result<Device> {
debug!("Hey there");
let host = cpal::default_host();
host.default_input_device()
.ok_or_else(|| anyhow!("No default output device"))

View File

@ -5,7 +5,7 @@ use figment::{
providers::{Env, Format, Toml},
Figment,
};
use log::{debug, error, trace, warn};
use log::{debug, error, trace};
use serde::{Deserialize, Serialize};
use tauri::api::path::config_dir;

View File

@ -8,8 +8,7 @@ use std::path::Path;
use anyhow::Result;
use base64::prelude::*;
use image::codecs::gif::{GifDecoder, GifEncoder, Repeat};
use image::imageops::resize;
use image::{guess_format, AnimationDecoder, Frame, ImageDecoder, ImageFormat};
use image::{guess_format, AnimationDecoder, ImageFormat};
use log::{debug, error};
use ray_format::Ray;
use serde::{Deserialize, Serialize};

View File

@ -2,7 +2,7 @@
"build": {
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build",
"devPath": "http://localhost:1420",
"devPath": "http://localhost:5173",
"distDir": "../build"
},
"package": {

View File

@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover" data-theme="hamlindigo">
<body data-sveltekit-preload-data="hover" data-theme="hamlindigo" class="overflow-hidden">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@ -2,3 +2,7 @@
@tailwind components;
@tailwind utilities;
@tailwind variants;
.dark body {
@apply bg-transparent;
}

View File

@ -4,13 +4,10 @@ import { mode } from './store';
export const micLevel = writable(0);
export const micThreshold = writable(0);
export const initAudio = async () => {
export const initAudio = async (): Promise<NodeJS.Timeout | undefined> => {
if (mode == 'tauri') {
const { invoke } = await import('@tauri-apps/api');
setInterval(async () => {
const newLevel = (await invoke('get_audio_level')) as number;
micLevel.set(newLevel);
}, 40);
micThreshold.subscribe(async (threshold) => {
try {
@ -19,45 +16,50 @@ export const initAudio = async () => {
await invoke('log', { msg: `Error setting mic threshold: ${e}` });
}
});
return setInterval(async () => {
const newLevel = (await invoke('get_audio_level')) as number;
micLevel.set(newLevel * 100);
}, 40);
} else {
await webAudio();
return await webAudio();
}
};
let audioCtx: AudioContext | undefined = undefined;
const webAudio = async () => {
const { createEventDispatcher } = await import('svelte');
const audioCtx = new AudioContext();
if(audioCtx) audioCtx.close();
audioCtx = new AudioContext();
if (navigator.mediaDevices) {
try {
const device = await navigator.mediaDevices.getUserMedia({ audio: true });
const mic = audioCtx.createMediaStreamSource(device);
const gain = audioCtx.createGain();
mic.connect(gain);
const analyzer = audioCtx.createAnalyser();
analyzer.fftSize = 2048;
mic.connect(analyzer);
gain.connect(analyzer);
gain.gain.setValueAtTime(1, audioCtx.currentTime);
const buffer = new Uint8Array(analyzer.frequencyBinCount);
const monitor = () => {
analyzer.getByteFrequencyData(buffer);
const max = buffer.reduce((prev, cur) => prev > cur ? prev : cur);
console.log(max);
micLevel.update((level) => level + (max * 0.8));
const max = (buffer.reduce((prev, cur) => prev + cur) / buffer.length);
micLevel.set(max);
micThreshold.subscribe((threshold) => {
if(max > threshold) {
console.log(max, threshold * 100 - 1);
if(max > (threshold * 100) - 1) {
dispatchEvent(new Event('mouth-open'))
} else {
dispatchEvent(new Event('mouth-close'));
}
})();
requestAnimationFrame(monitor);
}
monitor();
return setInterval(monitor);
} catch (e) {
console.error("Failed to get microphone: ", e);

View File

@ -1,170 +1,151 @@
<script lang="ts" context="module">
const hints = [
"Eyes open | Mouth closed",
"Eyes open | Mouth open",
"Eyes closed | Mouth closed",
"Eyes closed | Mouth open",
];
const hints = [
'Eyes open | Mouth closed',
'Eyes open | Mouth open',
'Eyes closed | Mouth closed',
'Eyes closed | Mouth open'
];
let loading = [false, false, false, false];
let loading = [false, false, false, false];
</script>
<script lang="ts">
import { frames } from "../store";
import { fs, invoke } from "@tauri-apps/api";
import { fade } from "svelte/transition";
import Context from "./context.svelte";
import LoadingSVG from "$lib/svg/loading.svg";
import Image from "image-js";
import { Gif } from "../gifler";
export let index: number;
import { frames } from '../store';
import { fs, invoke } from '@tauri-apps/api';
import { fade } from 'svelte/transition';
import Context from './context.svelte';
import Image from 'image-js';
import { Gif } from '../gifler';
export let index: number;
let menuTimeout: NodeJS.Timeout | null = null;
let showMenu = false;
let src: string | null;
let menuTimeout: NodeJS.Timeout | null = null;
let showMenu = false;
let src: string | null;
$: {
const frame = $frames[index];
if (!frame) src = null;
else {
if (frame?.kind === "still") src = frame.value.toDataURL();
else {
const b = new Blob([new Uint8Array(frame.data)]);
src = URL.createObjectURL(b);
}
}
}
$: {
const frame = $frames[index];
if (!frame) src = null;
else {
if (frame?.kind === 'still') src = frame.value.toDataURL();
else {
const b = new Blob([new Uint8Array(frame.data)]);
src = URL.createObjectURL(b);
}
}
}
const openImage = async () => {
loading[index] = true;
const path = (await invoke("open_image")) as {
kind: "png" | "gif";
data: string;
};
const openImage = async () => {
loading[index] = true;
const path = (await invoke('open_image')) as {
kind: 'png' | 'gif';
data: string;
};
if (path) {
const data = await fs.readBinaryFile(path.data);
if (path.kind == "png")
$frames[index] = {
kind: "still",
value: await Image.load(data),
};
else {
$frames[index] = {
kind: "GIF",
value: new Gif(new Promise((res) => res(data))),
data: data,
};
}
} else {
console.error("Error loading image");
}
loading[index] = false;
};
const clearImage = () => {
$frames[index] = null;
};
if (path) {
const data = await fs.readBinaryFile(path.data);
if (path.kind == 'png')
$frames[index] = {
kind: 'still',
value: await Image.load(data)
};
else {
$frames[index] = {
kind: 'GIF',
value: new Gif(new Promise((res) => res(data))),
data: data
};
}
} else {
console.error('Error loading image');
}
loading[index] = false;
};
const clearImage = () => {
$frames[index] = null;
};
</script>
<div class="box">
<button
class="preview"
on:click={(e) => {
if (e.shiftKey) {
clearImage();
} else {
openImage();
}
}}
on:mouseenter={() =>
(menuTimeout = setTimeout(() => (showMenu = true), 200))}
on:mouseleave={() => {
if (menuTimeout) {
clearTimeout(menuTimeout);
}
showMenu = false;
}}
>
{#if loading[index]}
<img class="loading" src="/loading.svg" alt="Loading" />
{:else if src}
<img {src} alt="Frame {{ index }}" />
{/if}
</button>
{#if showMenu}
<div transition:fade={{ duration: 50 }} class="context">
<Context>
<p>{hints[index]}</p>
</Context>
</div>
{/if}
</div>
<button
class="btn btn-xl w-32 bg-surface-100 aspect-square"
on:click={(e) => {
if (e.shiftKey) {
clearImage();
} else {
openImage();
}
}}
on:mouseenter={() => (menuTimeout = setTimeout(() => (showMenu = true), 200))}
on:mouseleave={() => {
if (menuTimeout) {
clearTimeout(menuTimeout);
}
showMenu = false;
}}
>
{#if loading[index]}
<img class="loading" src="/loading.svg" alt="Loading" />
{:else if src}
<img {src} alt="Frame {{ index }}" />
{/if}
</button>
<style lang="scss">
@-webkit-keyframes rotating {
from {
-webkit-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes rotating {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@-webkit-keyframes rotating {
from {
-webkit-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes rotating {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.loading {
-webkit-animation: rotating 2s linear infinite;
-moz-animation: rotating 2s linear infinite;
-ms-animation: rotating 2s linear infinite;
-o-animation: rotating 2s linear infinite;
animation: rotating 2s linear infinite;
}
$bg: rgba(150, 150, 150, 0.5);
.preview {
&:hover {
background-color: darken($color: $bg, $amount: 5%);
}
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
border: 2px solid black;
width: 100%;
height: 100%;
background-color: $bg;
user-select: none;
cursor: pointer;
}
$bg: rgba(150, 150, 150, 0.5);
.preview {
&:hover {
background-color: darken($color: $bg, $amount: 5%);
}
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
border: 2px solid black;
width: 100%;
height: 100%;
background-color: $bg;
user-select: none;
cursor: pointer;
}
.box {
width: 15vh;
height: 15vh;
user-select: none;
gap: 10px;
display: flex;
align-content: center;
justify-content: center;
.context {
z-index: 900;
position: absolute;
left: 105%;
width: max-content;
}
}
.box {
width: 15vh;
height: 15vh;
user-select: none;
gap: 10px;
display: flex;
align-content: center;
justify-content: center;
.context {
z-index: 900;
position: absolute;
left: 105%;
width: max-content;
}
}
img {
width: 75%;
user-select: none;
}
img {
width: 75%;
user-select: none;
}
</style>

View File

@ -10,9 +10,11 @@
export let onSetpointChange = (_: number) => {};
export let barColor: string;
const handleXPos = 0;
let bar: HTMLDivElement;
let point: HTMLDivElement;
let pos = { x: 0, y: 0 };
let pos = { x: handleXPos, y: 0 };
const tweenedProgress = tweened(0, { duration: 100, easing: sineOut });
onMount(async () => {
@ -34,10 +36,6 @@
$tweenedProgress = pxProgress;
}
}
setInterval(() => {
value = (value + 1) % 100;
}, 100)
</script>
<svelte:window
@ -46,7 +44,7 @@
// msg: `Resized`,
// });
pos = { x: 0, y: 0 };
pos = { x: handleXPos, y: 0 };
let rect = bar.getBoundingClientRect();
let prect = point.getBoundingClientRect();
pos.y = -(setpoint / 100) * rect.height + prect.height;
@ -54,13 +52,16 @@
/>
<div class="h-full flex flex-col gap-4">
<div bind:this={bar} class="bar-container rounded-container-token bg-surface-500 border-token border-surface-500-400-token">
<div
id="bar-container"
bind:this={bar}
class="w-10 flex flex-col justify-end rounded-container-token bg-surface-500 border-token border-surface-500-400-token h-5/6"
>
{#if withSetpoint}
<div
bind:this={point}
class="setpoint"
class="setpoint bg-slate-900 w-9 rounded-token"
use:draggable={{
// onDragStart: () => (pos = undefined),
handle: '.handle',
position: pos,
axis: 'y',
@ -73,53 +74,37 @@
onSetpointChange(1.0 - y);
}}
>
<div class="handle" />
<div class="handle rounded" />
</div>
{/if}
<div class="bar rounded-token {barColor ? barColor : "bg-primary-500"}" style="height: {$tweenedProgress}px;" />
<div
id="bar"
class="bar rounded-token {barColor ? barColor : 'bg-primary-500'}"
style="height: {$tweenedProgress}px;"
/>
</div>
<div class="aspect-square w-full">
<slot />
</div>
<slot class="icon" />
</div>
<style lang="scss">
.box {
display: flex;
flex-direction: column;
align-items: center;
gap: 1vh;
:global(img) {
width: var(--bar-width, 5vh);
}
}
.setpoint {
position: absolute;
background-color: black;
width: var(--bar-width, 5vh);
height: 0.5vh;
.handle {
position: absolute;
border-top: 2vh solid transparent;
border-bottom: 2vh solid transparent;
border-left: 2vh solid var(--handle-color, forestgreen);
left: -2.2vh;
top: 50%;
transform: translateY(-50%);
}
}
border-top: 1rem solid transparent;
border-bottom: 1rem solid transparent;
border-left: 1.5rem solid var(--handle-color, forestgreen);
.bar-container {
// border-radius: var(--bar-radius, 0px);
display: flex;
flex-direction: column;
justify-content: flex-end;
height: 80vh;
width: var(--bar-width, 5vh);
top: 50%;
transform: translateY(-50%) translateX(-1.7rem);
}
}
.bar {
user-select: none;
width: 100%;
border: 0;
margin: 0;
transition: background-color 0.1s;

View File

@ -8,10 +8,17 @@ export const active = derived(
([$level, $threshold]) => $level > $threshold
);
export const blink = writable(false);
export const transparent = writable(false);
export const initState = async () => {
if (mode == 'tauri') {
const { appWindow } = await import('@tauri-apps/api/window');
const { appWindow, PhysicalSize } = await import('@tauri-apps/api/window');
await appWindow.center();
await appWindow.setMinSize(new PhysicalSize(720, 600));
await appWindow.onFocusChanged(({ payload: focused }) => {
transparent.set(!focused);
});
await appWindow.listen('mouth-open', () => {
activationLevel.set(100);

View File

@ -1,7 +1,7 @@
<script lang="ts">
import FramePreview from '$lib/components/FramePreview.svelte';
import { quintInOut } from 'svelte/easing';
import { initAudio, micLevel, micThreshold } from "$lib/audio";
import { initAudio, micLevel, micThreshold } from '$lib/audio';
import '../app.postcss';
// Floating UI for Popups
@ -9,97 +9,129 @@
import { storePopup } from '@skeletonlabs/skeleton';
import { fly } from 'svelte/transition';
import Bar from '$lib/components/bar.svelte';
import { onMount } from 'svelte';
import { active, initState, closeThreshold, activationLevel } from '$lib/state';
import { onDestroy, onMount } from 'svelte';
import { active, initState, closeThreshold, activationLevel, transparent } from '$lib/state';
storePopup.set({ computePosition, autoUpdate, flip, shift, offset, arrow });
let deinitAudio: NodeJS.Timeout | undefined = undefined;
onMount(async () => {
await initAudio();
deinitAudio = await initAudio();
await initState();
})
});
$: transparent = false;
onDestroy(() => {
if (deinitAudio) clearInterval(deinitAudio);
});
$: bgColor = $transparent ? 'bg-pink-' : 'bg-surface-900';
</script>
<div class="container" class:transparent>
<!-- <div
transition:fly={{
duration: 200,
x: -200,
opacity: 100,
easing: quintInOut
}}
class="frames"
>
{#each [0, 1, 2, 3] as i}
<FramePreview index={i} />
{/each}
</div> -->
<div class="{bgColor} w-screen h-screen" id="container">
<slot />
<div
transition:fly={{
duration: 200,
x: 200,
opacity: 100,
easing: quintInOut,
}}
class="audio flex absolute right-6 gap-10 top-1/2 translate-y-[-50%]"
>
{#key $micThreshold}
<Bar
bind:value={$micLevel}
setpoint={$micThreshold * 100}
withSetpoint
onSetpointChange={async (e) => {
$micThreshold = e;
console.log(e);
}}
barColor={$active ? "bg-success-500" : "bg-tertiary-500"}
><img src="mic.svg" alt="microphone" /></Bar
>
{/key}
{#key $closeThreshold}
<Bar
withSetpoint
value={$activationLevel}
setpoint={$closeThreshold}
onSetpointChange={(y) => ($closeThreshold = y * 100)}
><img src="mouth.svg" alt="mouth" /></Bar
>
{/key}
</div>
{#if !$transparent}
<div
transition:fly={{
duration: 200,
x: -200,
opacity: 100,
easing: quintInOut
}}
class="flex flex-col justify-around h-full mx-10"
>
{#each [0, 1, 2, 3] as i}
<FramePreview index={i} />
{/each}
</div>
<div
transition:fly={{
duration: 200,
x: 200,
opacity: 100,
easing: quintInOut
}}
class="audio flex absolute right-6 gap-10 top-1/2 translate-y-[-50%] h-5/6"
>
{#key $micThreshold}
<Bar
value={$micLevel}
setpoint={$micThreshold * 100}
withSetpoint
onSetpointChange={async (e) => {
$micThreshold = e;
console.log(e);
}}
barColor={$active ? 'bg-success-500' : 'bg-blue-500'}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class:stroke-success-500={$active}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 18.75a6 6 0 006-6v-1.5m-6 7.5a6 6 0 01-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 01-3-3V4.5a3 3 0 116 0v8.25a3 3 0 01-3 3z"
/>
</svg>
</Bar>
{/key}
{#key $closeThreshold}
<Bar
withSetpoint
value={$activationLevel}
setpoint={$closeThreshold}
onSetpointChange={(y) => ($closeThreshold = y * 100)}
barColor={'bg-blue-500'}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19.114 5.636a9 9 0 010 12.728M16.463 8.288a5.25 5.25 0 010 7.424M6.75 8.25l4.72-4.72a.75.75 0 011.28.53v15.88a.75.75 0 01-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.01 9.01 0 012.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75z"
/>
</svg>
</Bar>
{/key}
</div>
{/if}
</div>
<!--
<style lang="scss">
@keyframes fade-out {
0% {
background-color: var(--active-color);
}
100% {
background-color: var(--inactive-color);
}
}
// @keyframes fade-out {
// 0% {
// background-color: var(--active-color);
// }
// 100% {
// background-color: var(--inactive-color);
// }
// }
@keyframes fade-in {
0% {
background-color: var(--inactive-color);
}
100% {
background-color: var(--active-color);
}
}
.container {
background-color: var(--active-color);
animation-name: fade-in;
animation-duration: 0.2s;
}
// @keyframes fade-in {
// 0% {
// background-color: var(--inactive-color);
// }
// 100% {
// background-color: var(--active-color);
// }
// }
// .container {
// animation-name: fade-in;
// animation-duration: 0.2s;
// }
.container.transparent {
animation-name: fade-out;
animation-duration: 0.2s;
background-color: var(--inactive-color);
}
// .container.transparent {
// animation-name: fade-out;
// animation-duration: 0.2s;
// }
.frames {
align-items: left;
@ -111,4 +143,4 @@
justify-content: space-between;
flex-direction: column;
}
</style>
</style> -->

View File

@ -1,50 +1,3 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 330 330" style="enable-background:new 0 0 330 330;" xml:space="preserve">
<g id="XMLID_894_">
<g id="XMLID_895_">
<path id="XMLID_896_" d="M164.998,210c35.888,0,65.085-29.195,65.085-65.12l-0.204-80c0-35.775-29.105-64.88-64.881-64.88
c-35.773,0-64.877,29.105-64.877,64.842l-0.203,80.076C99.918,180.805,129.112,210,164.998,210z M130.121,64.88
c0-19.233,15.646-34.88,34.877-34.88c19.233,0,34.881,15.647,34.881,34.919l0.204,80c0,19.344-15.739,35.081-35.085,35.081
c-19.343,0-35.08-15.737-35.08-35.044L130.121,64.88z"/>
</g>
<g id="XMLID_899_">
<path id="XMLID_900_" d="M280.084,154.96c0-8.284-6.716-15-15-15c-8.284,0-15,6.716-15,15c0,46.732-37.878,84.774-84.546,85.068
c-0.181-0.006-0.357-0.027-0.54-0.027c-0.183,0-0.359,0.021-0.541,0.027c-46.665-0.293-84.541-38.335-84.541-85.068
c0-8.284-6.716-15-15-15s-15,6.716-15,15c0,58.373,43.688,106.731,100.082,114.104V300H117c-8.284,0-15,6.716-15,15
s6.716,15,15,15h96.001c8.284,0,15-6.716,15-15s-6.716-15-15-15h-33.003v-30.936C236.395,261.69,280.084,213.332,280.084,154.96z"
/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 18.75a6 6 0 006-6v-1.5m-6 7.5a6 6 0 01-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 01-3-3V4.5a3 3 0 116 0v8.25a3 3 0 01-3 3z" />
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 334 B

View File

@ -1 +1,3 @@
<svg width="64px" height="64px" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--emojione-monotone" preserveAspectRatio="xMidYMid meet"><path d="M32 2C15.432 2 2 15.432 2 32s13.432 30 30 30s30-13.432 30-30S48.568 2 32 2zm0 57.5C16.836 59.5 4.5 47.164 4.5 32S16.836 4.5 32 4.5S59.5 16.836 59.5 32S47.164 59.5 32 59.5z" fill="currentColor"></path><circle cx="32" cy="45.139" r="7" fill="currentColor"></circle><circle cx="20.248" cy="25.045" r="4.5" fill="currentColor"></circle><circle cx="42.75" cy="25.045" r="4.5" fill="currentColor"></circle></svg>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.114 5.636a9 9 0 010 12.728M16.463 8.288a5.25 5.25 0 010 7.424M6.75 8.25l4.72-4.72a.75.75 0 011.28.53v15.88a.75.75 0 01-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.01 9.01 0 012.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75z" />
</svg>

Before

Width:  |  Height:  |  Size: 654 B

After

Width:  |  Height:  |  Size: 462 B

View File

@ -18,7 +18,7 @@ export default {
preset: [
{
name: 'hamlindigo',
enhancements: true,
enhancements: false,
},
],
},