diff --git a/apps/desktop/src/window/QuitHold.test.ts b/apps/desktop/src/window/QuitHold.test.ts
index c4bf2f34b0a1..58809d8eb14d 100644
--- a/apps/desktop/src/window/QuitHold.test.ts
+++ b/apps/desktop/src/window/QuitHold.test.ts
@@ -252,24 +252,31 @@ describe("makeQuitShortcutHandler", () => {
expect(harness.notifications).toEqual([]);
});
- it("honors a quick double press when both key releases beat their mode reads", async () => {
- const resolvers: Array<(mode: QuitConfirmationMode) => void> = [];
- const harness = makeHarness({
- getMode: () => new Promise((resolve) => resolvers.push(resolve)),
- });
- await harness.send(makeInput({}));
- await harness.send(makeInput({ type: "keyUp" }));
- vi.advanceTimersByTime(QUIT_DOUBLE_PRESS_MS - 100);
- await harness.send(makeInput({}));
- await harness.send(makeInput({ type: "keyUp" }));
-
- resolvers[1]?.("double-click");
- await Promise.resolve();
- await Promise.resolve();
-
- expect(harness.quit).toHaveBeenCalledTimes(1);
- expect(harness.notifications).toEqual([]);
- });
+ it.each(["direct", "hold", "double-click"] as const)(
+ "quits on a quick second press without waiting for a pending %s mode read",
+ async (mode) => {
+ const resolvers: Array<(mode: QuitConfirmationMode) => void> = [];
+ const harness = makeHarness({
+ getMode: () => new Promise((resolve) => resolvers.push(resolve)),
+ });
+ await harness.send(makeInput({}));
+ await harness.send(makeInput({ type: "keyUp" }));
+ vi.advanceTimersByTime(QUIT_DOUBLE_PRESS_MS - 100);
+ await harness.send(makeInput({}));
+
+ expect(harness.quit).toHaveBeenCalledTimes(1);
+ expect(harness.notifications).toEqual([]);
+
+ await harness.send(makeInput({ type: "keyUp" }));
+
+ resolvers[0]?.(mode);
+ await Promise.resolve();
+ await Promise.resolve();
+
+ expect(harness.quit).toHaveBeenCalledTimes(1);
+ expect(harness.notifications).toEqual([]);
+ },
+ );
it("discards a stale mode resolution from a superseded press", async () => {
// Press #1's mode is still pending when the user releases and
@@ -378,6 +385,38 @@ describe("makeQuitShortcutHandler", () => {
expect(harness.notifications).toEqual([HOLD_DOWN, UP]);
});
+ it("quits on a quick second press in hold mode when the first release is unseen", async () => {
+ const harness = makeHarness();
+ await harness.send(makeInput({}));
+ vi.advanceTimersByTime(QUIT_DOUBLE_PRESS_MS - 100);
+ await harness.send(makeInput({}));
+
+ expect(harness.concealWindow).not.toHaveBeenCalled();
+ expect(harness.quit).toHaveBeenCalledTimes(1);
+ expect(harness.notifications).toEqual([HOLD_DOWN, UP]);
+ });
+
+ it("does not count auto-repeat as a second press", async () => {
+ const harness = makeHarness();
+ await harness.send(makeInput({}));
+ await harness.holdFor(QUIT_DOUBLE_PRESS_MS - 100);
+
+ expect(harness.quit).not.toHaveBeenCalled();
+ expect(harness.notifications).toEqual([HOLD_DOWN]);
+ });
+
+ it("does not count a released tap after another shortcut interrupts it", async () => {
+ const harness = makeHarness();
+ await harness.send(makeInput({}));
+ await harness.send(makeInput({ type: "keyUp" }));
+ await harness.send(makeInput({ key: "c" }));
+ vi.advanceTimersByTime(100);
+ await harness.send(makeInput({}));
+
+ expect(harness.quit).not.toHaveBeenCalled();
+ expect(harness.notifications).toEqual([HOLD_DOWN, UP, HOLD_DOWN]);
+ });
+
it("cancels the hold when another key interrupts it", async () => {
const harness = makeHarness();
await harness.send(makeInput({}));
diff --git a/apps/desktop/src/window/QuitHold.ts b/apps/desktop/src/window/QuitHold.ts
index 4095e3d4354b..a995184ddd70 100644
--- a/apps/desktop/src/window/QuitHold.ts
+++ b/apps/desktop/src/window/QuitHold.ts
@@ -12,7 +12,8 @@ export const QUIT_DOUBLE_PRESS_MS = 500;
// tap release can go completely unseen and a release-based timer would quit
// anyway. Once held, quitting waits for Q keyUp or a quiet grace period after
// repeats stop so they cannot reach the next app. Keyboards with
-// auto-repeat disabled fall back to the application menu Quit action.
+// auto-repeat disabled must use a double press or the application menu Quit action.
+// Supporting holds without repeats requires a native physical key-state check.
export const QUIT_HOLD_RELEASE_GRACE_MS = 600;
// A slow repeat rate can exceed the fixed grace. Waiting for two observed
// cadences keeps the timer behind the next repeat without slowing normal rates.
@@ -52,8 +53,8 @@ export function makeQuitShortcutHandler(
let lastRepeatAt = 0;
let repeatCadenceMs = 0;
// Incremented when a press is superseded or explicitly cancelled. A plain
- // key release does not invalidate its pending mode read: direct mode and a
- // completed second press must still be honored after that read settles.
+ // key release does not invalidate its pending mode read: a direct-mode
+ // press must still quit after that read settles.
let generation = 0;
const clearWatchdog = () => {
@@ -64,9 +65,9 @@ export function makeQuitShortcutHandler(
};
const release = (cancelPendingMode = true, keepDoublePressHint = false) => {
+ if (cancelPendingMode) generation += 1;
if (!holding && !notified) return;
const keepHint = keepDoublePressHint && mode === "double-click" && notified;
- if (cancelPendingMode) generation += 1;
holding = false;
armed = false;
quitOnRelease = false;
@@ -85,6 +86,7 @@ export function makeQuitShortcutHandler(
// Dismisses any overlay first so a cancelled quit cannot leave a stale hint.
const quitNow = () => {
release();
+ lastPressAt = 0;
options.quit();
};
@@ -138,13 +140,10 @@ export function makeQuitShortcutHandler(
// quit shortcut, so it must not cancel an active double-press window.
if (key === modifierKey && !input.alt && !input.shift) return;
- // Any other key (or an extra modifier) pressed mid-hold breaks the
- // gesture; without this the hold timer keeps running through the
- // interruption and the next qualifying repeat would quit early. The
- // interrupted press also stops counting toward a double press, but only
- // here, not in release(), which runs mid-restart on an unseen-release
- // re-press and must not wipe that press's own tap timestamp.
- if ((holding || notified) && !input.isAutoRepeat) {
+ // Other keys cancel the hold and the first tap, even after release.
+ // Keep this separate from release(), which also runs when a fresh Q
+ // keydown follows a keyUp that macOS did not deliver.
+ if (!input.isAutoRepeat) {
lastPressAt = 0;
release();
}
@@ -171,6 +170,13 @@ export function makeQuitShortcutHandler(
if (holding || notified) release();
generation += 1;
+ // Every mode accepts two presses. Quit before reading settings so a slow
+ // read cannot delay the second press. Repeats never reach this branch.
+ if (previousPressAt !== 0 && now - previousPressAt <= QUIT_DOUBLE_PRESS_MS) {
+ quitNow();
+ return;
+ }
+
const pressGeneration = generation;
holding = true;
heldSince = now;
@@ -181,13 +187,6 @@ export function makeQuitShortcutHandler(
quitNow();
return;
}
- // Keep a second press as an escape hatch when macOS misses the events
- // that would complete a hold.
- if (previousPressAt !== 0 && now - previousPressAt <= QUIT_DOUBLE_PRESS_MS) {
- quitNow();
- return;
- }
-
if (resolvedMode === "double-click") {
const remainingMs = QUIT_DOUBLE_PRESS_MS - (Date.now() - now);
if (remainingMs <= 0) {
diff --git a/apps/marketing/package.json b/apps/marketing/package.json
index 912faf88164d..78c121d14fe2 100644
--- a/apps/marketing/package.json
+++ b/apps/marketing/package.json
@@ -11,7 +11,8 @@
},
"dependencies": {
"@t3tools/shared": "workspace:*",
- "astro": "^7.0.3"
+ "astro": "^7.0.3",
+ "sharp": "0.34.5"
},
"devDependencies": {
"@astrojs/check": "^0.9.7",
diff --git a/apps/marketing/public/fonts/dm-sans-OFL.txt b/apps/marketing/public/fonts/dm-sans-OFL.txt
new file mode 100644
index 000000000000..a0eedfbf0df0
--- /dev/null
+++ b/apps/marketing/public/fonts/dm-sans-OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2014 The DM Sans Project Authors (https://github.com/googlefonts/dm-fonts)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+https://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/apps/marketing/public/fonts/jetbrains-mono-OFL.txt b/apps/marketing/public/fonts/jetbrains-mono-OFL.txt
new file mode 100644
index 000000000000..821a3dac22af
--- /dev/null
+++ b/apps/marketing/public/fonts/jetbrains-mono-OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+
+This license is copied below, and is also available with a FAQ at: https://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/apps/marketing/public/app-desktop.webp b/apps/marketing/src/assets/app-desktop.webp
similarity index 100%
rename from apps/marketing/public/app-desktop.webp
rename to apps/marketing/src/assets/app-desktop.webp
diff --git a/apps/marketing/src/assets/fonts/dm-sans-latin-ext.woff2 b/apps/marketing/src/assets/fonts/dm-sans-latin-ext.woff2
new file mode 100644
index 000000000000..6e84cba1949c
Binary files /dev/null and b/apps/marketing/src/assets/fonts/dm-sans-latin-ext.woff2 differ
diff --git a/apps/marketing/src/assets/fonts/dm-sans-latin.woff2 b/apps/marketing/src/assets/fonts/dm-sans-latin.woff2
new file mode 100644
index 000000000000..c75f7bf1b465
Binary files /dev/null and b/apps/marketing/src/assets/fonts/dm-sans-latin.woff2 differ
diff --git a/apps/marketing/src/assets/fonts/jetbrains-mono-cyrillic-ext.woff2 b/apps/marketing/src/assets/fonts/jetbrains-mono-cyrillic-ext.woff2
new file mode 100644
index 000000000000..289de93149ab
Binary files /dev/null and b/apps/marketing/src/assets/fonts/jetbrains-mono-cyrillic-ext.woff2 differ
diff --git a/apps/marketing/src/assets/fonts/jetbrains-mono-cyrillic.woff2 b/apps/marketing/src/assets/fonts/jetbrains-mono-cyrillic.woff2
new file mode 100644
index 000000000000..de39acbc4069
Binary files /dev/null and b/apps/marketing/src/assets/fonts/jetbrains-mono-cyrillic.woff2 differ
diff --git a/apps/marketing/src/assets/fonts/jetbrains-mono-greek.woff2 b/apps/marketing/src/assets/fonts/jetbrains-mono-greek.woff2
new file mode 100644
index 000000000000..a53d52d6d02c
Binary files /dev/null and b/apps/marketing/src/assets/fonts/jetbrains-mono-greek.woff2 differ
diff --git a/apps/marketing/src/assets/fonts/jetbrains-mono-latin-ext.woff2 b/apps/marketing/src/assets/fonts/jetbrains-mono-latin-ext.woff2
new file mode 100644
index 000000000000..82f96681c076
Binary files /dev/null and b/apps/marketing/src/assets/fonts/jetbrains-mono-latin-ext.woff2 differ
diff --git a/apps/marketing/src/assets/fonts/jetbrains-mono-latin.woff2 b/apps/marketing/src/assets/fonts/jetbrains-mono-latin.woff2
new file mode 100644
index 000000000000..4d09cda4a4c3
Binary files /dev/null and b/apps/marketing/src/assets/fonts/jetbrains-mono-latin.woff2 differ
diff --git a/apps/marketing/src/assets/fonts/jetbrains-mono-vietnamese.woff2 b/apps/marketing/src/assets/fonts/jetbrains-mono-vietnamese.woff2
new file mode 100644
index 000000000000..e38f5538f84e
Binary files /dev/null and b/apps/marketing/src/assets/fonts/jetbrains-mono-vietnamese.woff2 differ
diff --git a/apps/marketing/public/icon.webp b/apps/marketing/src/assets/icon.webp
similarity index 100%
rename from apps/marketing/public/icon.webp
rename to apps/marketing/src/assets/icon.webp
diff --git a/apps/marketing/src/layouts/Layout.astro b/apps/marketing/src/layouts/Layout.astro
index 686b555fd4c0..ca5ea15f61de 100644
--- a/apps/marketing/src/layouts/Layout.astro
+++ b/apps/marketing/src/layouts/Layout.astro
@@ -1,4 +1,8 @@
---
+import { Image } from "astro:assets";
+import appIcon from "../assets/icon.webp";
+import dmSansLatinUrl from "../assets/fonts/dm-sans-latin.woff2?url";
+import "../styles/fonts.css";
import {
ANDROID_PLAY_STORE_URL,
GITHUB_REPOSITORY_URL,
@@ -24,11 +28,12 @@ const {
-
-
@@ -67,7 +72,17 @@ const {