WaylandKeybinds: localized info panel with setup steps
- Info panel in the plugin modal: live status with the number of keybinds GNOME registered, the three setup steps and a button that opens Discord's Keybinds page (closes only the plugin modal, which otherwise blocks settings navigation). - Follows Discord's language: English, Portuguese (pt-BR) and Spanish (es-ES and es-419 use each region's action names); bold labels quote Discord's own strings. - Shared helpers moved to discord.ts; copy and locale choice in copy.ts, covered by basis-path tests (41 tests). - README: current settings path (User Settings > System > Keybinds).
This commit is contained in:
1 parent
a3a9149184
commit
34b61e2509
7 files changed
+351
-18
No files matched your search
@@ -39,14 +39,14 @@ pnpm build
|
|||||||
pnpm inject
|
pnpm inject
|
||||||
```
|
```
|
||||||
|
|
||||||
Restart Discord. The plugin is on by default; you can find it as **WaylandKeybinds** in Vencord's plugin list.
|
Restart Discord. The plugin is on by default; you can find it as **WaylandKeybinds** in Vencord's plugin list. Its info panel shows whether the fix is active, how many keybinds GNOME has registered, and a button that opens Discord's Keybinds page.
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> Keep the folder name `waylandKeybinds.discordDesktop`. The suffix limits the plugin to the Discord desktop app, the only place where Discord's native keybind module exists.
|
> Keep the folder name `waylandKeybinds.discordDesktop`. The suffix limits the plugin to the Discord desktop app, the only place where Discord's native keybind module exists.
|
||||||
|
|
||||||
## Use
|
## Use
|
||||||
|
|
||||||
1. Open Discord **User Settings → Keybinds → Add a Keybind**.
|
1. Open Discord **User Settings → System → Keybinds** and click **Add a Keybind**.
|
||||||
2. Pick an action (Toggle Mute, Toggle Deafen, Push to Talk…) and press your combination.
|
2. Pick an action (Toggle Mute, Toggle Deafen, Push to Talk…) and press your combination.
|
||||||
3. GNOME asks once to allow the shortcut. Click **Add**.
|
3. GNOME asks once to allow the shortcut. Click **Add**.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2026 Evandro
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Button } from "@components/Button";
|
||||||
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
import { i18n, SettingsRouter, useState, useStateFromStores } from "@webpack/common";
|
||||||
|
|
||||||
|
import { pickCopy } from "./copy";
|
||||||
|
import { KeybindsStore, portalActive } from "./discord";
|
||||||
|
|
||||||
|
function locale(): string {
|
||||||
|
try {
|
||||||
|
return i18n.intl.currentLocale ?? document.documentElement.lang;
|
||||||
|
} catch {
|
||||||
|
return document.documentElement.lang;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the panel renders inside the plugin modal, so the top of Discord's modal stack at mount is that modal
|
||||||
|
const ModalStack = findByPropsLazy("getOpenModalKeys", "closeModal");
|
||||||
|
|
||||||
|
function topModalKey(): string | undefined {
|
||||||
|
try {
|
||||||
|
return [...ModalStack.getOpenModalKeys()].at(-1);
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openKeybinds(pluginModalKey: string | undefined) {
|
||||||
|
// settings ignore navigation while another modal sits on top of them
|
||||||
|
if (pluginModalKey) ModalStack.closeModal(pluginModalKey);
|
||||||
|
SettingsRouter.openUserSettings("system_custom_keybinds_category");
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatusIcon({ active }: { active: boolean; }) {
|
||||||
|
return (
|
||||||
|
<svg className="vc-wk-status-icon" viewBox="0 0 20 20" width="20" height="20" aria-hidden="true">
|
||||||
|
<circle cx="10" cy="10" r="8.25" fill="none" stroke="currentColor" strokeWidth="1.5" />
|
||||||
|
{active
|
||||||
|
? <path d="m6.5 10.25 2.4 2.4 4.6-5" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
|
: <path d="M10 9v4.5M10 6.25v.01" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" />}
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AboutPanel() {
|
||||||
|
const copy = pickCopy(locale());
|
||||||
|
const active = portalActive();
|
||||||
|
const [modalKey] = useState(topModalKey);
|
||||||
|
const registered = useStateFromStores([KeybindsStore], () =>
|
||||||
|
Object.keys(KeybindsStore.getUserAgnosticState() ?? {})
|
||||||
|
.filter(id => KeybindsStore.getWaylandPortalShortcutState(id)?.bound).length
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!active) {
|
||||||
|
return (
|
||||||
|
<p className="vc-wk-status">
|
||||||
|
<StatusIcon active={false} />
|
||||||
|
{copy.inactive}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="vc-wk-panel" aria-labelledby="vc-wk-title">
|
||||||
|
<p className="vc-wk-status vc-wk-status-on">
|
||||||
|
<StatusIcon active />
|
||||||
|
{copy.active(registered)}
|
||||||
|
</p>
|
||||||
|
<h3 id="vc-wk-title" className="vc-wk-title">{copy.title}</h3>
|
||||||
|
<p className="vc-wk-lead">{copy.lead}</p>
|
||||||
|
{/* list-style is removed for the numbered badges, so keep the list role for screen readers */}
|
||||||
|
<ol className="vc-wk-steps" role="list">
|
||||||
|
{copy.steps.map(([before, bold, after], i) => <li key={i}><span>{before}{bold && <b>{bold}</b>}{after}</span></li>)}
|
||||||
|
</ol>
|
||||||
|
<div className="vc-wk-actions">
|
||||||
|
<Button onClick={() => openKeybinds(modalKey)}>{copy.open}</Button>
|
||||||
|
<p className="vc-wk-tip">{copy.tip}</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2026 Evandro
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Labels in bold quote Discord's own strings for each locale, so users find them on screen
|
||||||
|
export interface Copy {
|
||||||
|
active(registered: number): string;
|
||||||
|
inactive: string;
|
||||||
|
title: string;
|
||||||
|
lead: string;
|
||||||
|
// [before, bold, after]: plain data, because JSX can't run before Vencord has loaded React
|
||||||
|
steps: [string, string, string][];
|
||||||
|
open: string;
|
||||||
|
tip: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EN: Copy = {
|
||||||
|
active: n => n === 0
|
||||||
|
? "Active on this system · no keybinds yet"
|
||||||
|
: `Active on this system · ${n} ${n === 1 ? "keybind" : "keybinds"} registered`,
|
||||||
|
inactive: "Not needed on this system: the fix only acts on Linux with Wayland.",
|
||||||
|
title: "Your Discord keybinds now work in every app",
|
||||||
|
lead: "Set them up the same way as on Windows, on Discord's own Keybinds page.",
|
||||||
|
steps: [
|
||||||
|
["Open ", "Keybinds", " under System in Discord's User Settings."],
|
||||||
|
["Click ", "Add a Keybind", ", choose an action such as Toggle Mute, Toggle Deafen or Push to Talk, and record your keys."],
|
||||||
|
["When your system asks to add the shortcut, ", "allow it", "."]
|
||||||
|
],
|
||||||
|
open: "Open Keybinds",
|
||||||
|
tip: "Push to Talk is most reliable on a single key without modifiers, such as Pause or F13."
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PT: Copy = {
|
||||||
|
active: n => n === 0
|
||||||
|
? "Ativo neste sistema · nenhum atalho ainda"
|
||||||
|
: `Ativo neste sistema · ${n} ${n === 1 ? "atalho registrado" : "atalhos registrados"}`,
|
||||||
|
inactive: "Não é necessário neste sistema: a correção só atua no Linux com Wayland.",
|
||||||
|
title: "Seus atalhos do Discord agora funcionam em qualquer aplicativo",
|
||||||
|
lead: "Configure do mesmo jeito que no Windows, na própria página de atalhos do Discord.",
|
||||||
|
steps: [
|
||||||
|
["Abra ", "Atalhos do teclado", " em Sistema, nas Configurações de usuário."],
|
||||||
|
["Clique em ", "Adicionar atalho", ", escolha a ação (Ativar/desativar microfone, Ativar/desativar áudio, Aperte para Falar…) e grave as teclas."],
|
||||||
|
["Quando o sistema pedir para adicionar o atalho, ", "permita", "."]
|
||||||
|
],
|
||||||
|
open: "Abrir Atalhos do teclado",
|
||||||
|
tip: "No Aperte para Falar, prefira uma tecla única sem modificadores, como Pause ou F13."
|
||||||
|
};
|
||||||
|
|
||||||
|
// Spain and Latin American Spanish name the voice actions differently in Discord
|
||||||
|
function spanish(mute: string, deafen: string): Copy {
|
||||||
|
return {
|
||||||
|
active: n => n === 0
|
||||||
|
? "Activo en este sistema · todavía no hay atajos"
|
||||||
|
: `Activo en este sistema · ${n} ${n === 1 ? "atajo registrado" : "atajos registrados"}`,
|
||||||
|
inactive: "No hace falta en este sistema: la corrección solo actúa en Linux con Wayland.",
|
||||||
|
title: "Tus atajos de Discord ahora funcionan en cualquier aplicación",
|
||||||
|
lead: "Configúralos igual que en Windows, en la propia página de atajos de Discord.",
|
||||||
|
steps: [
|
||||||
|
["Abre ", "Atajos de teclado", " en Sistema, dentro de los Ajustes de usuario."],
|
||||||
|
["Haz clic en ", "Añadir un atajo de teclado", `, elige la acción (${mute}, ${deafen}, Pulsar para hablar…) y graba las teclas.`],
|
||||||
|
["Cuando el sistema pida añadir el atajo, ", "permítelo", "."]
|
||||||
|
],
|
||||||
|
open: "Abrir Atajos de teclado",
|
||||||
|
tip: "Para Pulsar para hablar, mejor usa una sola tecla sin modificadores, como Pause o F13."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ES_ES = spanish("Alternar silencio", "Alternar ensordecimiento");
|
||||||
|
export const ES_419 = spanish("Activar o desactivar silencio", "Activar o desactivar ensordecer");
|
||||||
|
|
||||||
|
export function pickCopy(locale: string): Copy {
|
||||||
|
const lang = locale.toLowerCase();
|
||||||
|
if (lang.startsWith("pt")) return PT;
|
||||||
|
if (lang === "es-419") return ES_419;
|
||||||
|
if (lang.startsWith("es")) return ES_ES;
|
||||||
|
return EN;
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2026 Evandro
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { IS_LINUX } from "@utils/constants";
|
||||||
|
import { findStoreLazy } from "@webpack";
|
||||||
|
|
||||||
|
export type Shortcut = [number, number, ...unknown[]][];
|
||||||
|
|
||||||
|
export const KeybindsStore = findStoreLazy("KeybindsStore");
|
||||||
|
|
||||||
|
export function discordUtils() {
|
||||||
|
return DiscordNative.nativeModules.requireModule("discord_utils");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function portalActive(): boolean {
|
||||||
|
return IS_LINUX && discordUtils().isWaylandGlobalShortcutsAvailable?.() === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function portalTrigger(shortcut: Shortcut): string {
|
||||||
|
return discordUtils().getWaylandPortalTrigger?.(shortcut.map(([type, code]) => [type, code])) ?? "";
|
||||||
|
}
|
||||||
@@ -4,15 +4,14 @@
|
|||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IS_LINUX } from "@utils/constants";
|
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findStoreLazy } from "@webpack";
|
|
||||||
|
|
||||||
|
import { AboutPanel } from "./about";
|
||||||
|
import { KeybindsStore, portalActive, portalTrigger, Shortcut } from "./discord";
|
||||||
import { adjustOptions, Callback, createDispatcher, Dispatcher, fixPortalState, KeyEventOptions, planRegistration, PortalState, Scheduler } from "./logic";
|
import { adjustOptions, Callback, createDispatcher, Dispatcher, fixPortalState, KeyEventOptions, planRegistration, PortalState, Scheduler } from "./logic";
|
||||||
import { PATCHES } from "./patches";
|
import { PATCHES } from "./patches";
|
||||||
|
import managedStyle from "./style.css?managed";
|
||||||
type Shortcut = [number, number, ...unknown[]][];
|
|
||||||
|
|
||||||
// Discord's own native keybind bridge (the object the KeybindsStore registers through)
|
// Discord's own native keybind bridge (the object the KeybindsStore registers through)
|
||||||
interface Bridge {
|
interface Bridge {
|
||||||
@@ -28,28 +27,21 @@ const scheduler: Scheduler = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const logger = new Logger("WaylandKeybinds");
|
const logger = new Logger("WaylandKeybinds");
|
||||||
const KeybindsStore = findStoreLazy("KeybindsStore");
|
|
||||||
|
|
||||||
function discordUtils() {
|
|
||||||
return DiscordNative.nativeModules.requireModule("discord_utils");
|
|
||||||
}
|
|
||||||
|
|
||||||
function portalTrigger(shortcut: Shortcut): string {
|
|
||||||
return discordUtils().getWaylandPortalTrigger?.(shortcut.map(([type, code]) => [type, code])) ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "WaylandKeybinds",
|
name: "WaylandKeybinds",
|
||||||
description: "Makes Discord's global keybinds reliable on GNOME Wayland: toggles (mute, deafen) fire on key press and push-to-talk recovers from key releases GNOME drops",
|
description: "Makes Discord's keybinds work in every app on Linux Wayland: set them up in Discord's Keybinds page as usual and allow the system prompt",
|
||||||
authors: [{ name: "Evandro", id: 281463271758888960n }],
|
authors: [{ name: "Evandro", id: 281463271758888960n }],
|
||||||
// only changes behaviour when Discord registers keybinds through the Wayland GlobalShortcuts portal
|
// only changes behaviour when Discord registers keybinds through the Wayland GlobalShortcuts portal
|
||||||
enabledByDefault: true,
|
enabledByDefault: true,
|
||||||
patches: PATCHES,
|
patches: PATCHES,
|
||||||
|
managedStyle,
|
||||||
|
settingsAboutComponent: AboutPanel,
|
||||||
|
|
||||||
wrapRegistration(bridge: Bridge, id: number, shortcut: Shortcut, callback: Callback, options: KeyEventOptions): [Callback, KeyEventOptions] {
|
wrapRegistration(bridge: Bridge, id: number, shortcut: Shortcut, callback: Callback, options: KeyEventOptions): [Callback, KeyEventOptions] {
|
||||||
try {
|
try {
|
||||||
const portalActive = IS_LINUX && discordUtils().isWaylandGlobalShortcutsAvailable?.() === true;
|
const active = portalActive();
|
||||||
const mode = planRegistration(options, portalActive, portalActive && portalTrigger(shortcut) !== "");
|
const mode = planRegistration(options, active, active && portalTrigger(shortcut) !== "");
|
||||||
const adjusted = adjustOptions(options, mode);
|
const adjusted = adjustOptions(options, mode);
|
||||||
dispatchers.get(id)?.cancel();
|
dispatchers.get(id)?.cancel();
|
||||||
const dispatcher = createDispatcher(callback, mode, () => {
|
const dispatcher = createDispatcher(callback, mode, () => {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import { describe, test } from "node:test";
|
import { describe, test } from "node:test";
|
||||||
|
|
||||||
|
import { EN, ES_419, ES_ES, pickCopy, PT } from "./copy";
|
||||||
import { adjustOptions, createDispatcher, ECHO_MS, FIRST_PROBE_MS, fixPortalState, normalizeTrigger, planRegistration, PROBE_WINDOW_MS } from "./logic";
|
import { adjustOptions, createDispatcher, ECHO_MS, FIRST_PROBE_MS, fixPortalState, normalizeTrigger, planRegistration, PROBE_WINDOW_MS } from "./logic";
|
||||||
|
|
||||||
function recorder() {
|
function recorder() {
|
||||||
@@ -241,3 +242,50 @@ describe("fixPortalState (V(G)=5)", () => {
|
|||||||
assert.equal(fixPortalState(gnome, "CTRL+ALT+F12"), gnome);
|
assert.equal(fixPortalState(gnome, "CTRL+ALT+F12"), gnome);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("pickCopy (V(G)=4)", () => {
|
||||||
|
test("P1 português → PT", () => {
|
||||||
|
assert.equal(pickCopy("pt-BR"), PT);
|
||||||
|
});
|
||||||
|
test("P2 espanhol da América Latina → ES_419", () => {
|
||||||
|
assert.equal(pickCopy("es-419"), ES_419);
|
||||||
|
});
|
||||||
|
test("P3 espanhol da Espanha → ES_ES", () => {
|
||||||
|
assert.equal(pickCopy("es-ES"), ES_ES);
|
||||||
|
});
|
||||||
|
test("P4 inglês → EN", () => {
|
||||||
|
assert.equal(pickCopy("en-US"), EN);
|
||||||
|
});
|
||||||
|
test("borda: idioma fora dos três → EN", () => {
|
||||||
|
assert.equal(pickCopy("fr"), EN);
|
||||||
|
assert.equal(pickCopy("ja"), EN);
|
||||||
|
});
|
||||||
|
test("borda: idioma vazio → EN", () => {
|
||||||
|
assert.equal(pickCopy(""), EN);
|
||||||
|
});
|
||||||
|
test("borda: maiúsculas e minúsculas não importam", () => {
|
||||||
|
assert.equal(pickCopy("PT-br"), PT);
|
||||||
|
assert.equal(pickCopy("ES-419"), ES_419);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("textos do painel", () => {
|
||||||
|
test("botão citado no passo 2 é o rótulo oficial do Discord em cada idioma", () => {
|
||||||
|
assert.equal(EN.steps[1][1], "Add a Keybind");
|
||||||
|
assert.equal(PT.steps[1][1], "Adicionar atalho");
|
||||||
|
assert.equal(ES_ES.steps[1][1], "Añadir un atajo de teclado");
|
||||||
|
assert.equal(ES_419.steps[1][1], "Añadir un atajo de teclado");
|
||||||
|
});
|
||||||
|
test("as variantes de espanhol usam os nomes de ação de cada região", () => {
|
||||||
|
assert.match(ES_ES.steps[1][2], /Alternar silencio, Alternar ensordecimiento/);
|
||||||
|
assert.match(ES_419.steps[1][2], /Activar o desactivar silencio, Activar o desactivar ensordecer/);
|
||||||
|
});
|
||||||
|
test("plural do status", () => {
|
||||||
|
for (const copy of [EN, PT, ES_ES]) {
|
||||||
|
assert.notEqual(copy.active(0), copy.active(1));
|
||||||
|
assert.notEqual(copy.active(1).replace("1", "2"), copy.active(2));
|
||||||
|
}
|
||||||
|
assert.equal(ES_ES.active(1), "Activo en este sistema · 1 atajo registrado");
|
||||||
|
assert.equal(ES_ES.active(2), "Activo en este sistema · 2 atajos registrados");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
.vc-wk-panel {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 18px 20px 20px;
|
||||||
|
border: 1px solid var(--border-subtle);
|
||||||
|
border-radius: var(--radius-md, 12px);
|
||||||
|
background: var(--background-mod-subtle);
|
||||||
|
color: var(--text-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-wk-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-wk-status-icon {
|
||||||
|
flex: none;
|
||||||
|
color: var(--interactive-icon-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-wk-status-on {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-wk-status-on .vc-wk-status-icon {
|
||||||
|
color: var(--status-positive);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-wk-title {
|
||||||
|
margin: 2px 0 0;
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 26px;
|
||||||
|
color: var(--text-strong);
|
||||||
|
text-wrap: balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-wk-lead {
|
||||||
|
margin: -4px 0 0;
|
||||||
|
max-width: 62ch;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-wk-steps {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 6px 0 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
counter-reset: vc-wk-step;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-wk-steps li {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 24px 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
max-width: 66ch;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 22px;
|
||||||
|
counter-increment: vc-wk-step;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-wk-steps li::before {
|
||||||
|
content: counter(vc-wk-step);
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
margin-top: -1px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--background-mod-strong);
|
||||||
|
color: var(--text-strong);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-wk-steps b {
|
||||||
|
color: var(--text-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-wk-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px 16px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-wk-tip {
|
||||||
|
flex: 1 1 240px;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 18px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user