+
{drag.blank ? '' : drag.letter}
{/if}
@@ -1092,6 +1122,10 @@
pointer-events: none;
z-index: 60;
}
+ /* On touch the finger covers the tile, so enlarge the drag ghost ~1.5x (Stage 17). */
+ .ghost.touch {
+ transform: translate(-50%, -50%) scale(1.5);
+ }
.alpha {
display: grid;
grid-template-columns: repeat(6, 1fr);
diff --git a/ui/src/game/Rack.svelte b/ui/src/game/Rack.svelte
index 6a11f3b..e85295b 100644
--- a/ui/src/game/Rack.svelte
+++ b/ui/src/game/Rack.svelte
@@ -91,6 +91,10 @@
font-size: 1.4rem;
touch-action: none;
user-select: none;
+ -webkit-user-select: none;
+ /* iOS shows a tap/active highlight that can linger on the neighbour sliding into a
+ dragged tile's slot (Stage 17); suppress it so only our own styles mark a tile. */
+ -webkit-tap-highlight-color: transparent;
}
.tile.selected {
outline: 3px solid var(--accent);
diff --git a/ui/src/lib/app.svelte.ts b/ui/src/lib/app.svelte.ts
index 6957f46..c47efb1 100644
--- a/ui/src/lib/app.svelte.ts
+++ b/ui/src/lib/app.svelte.ts
@@ -13,6 +13,7 @@ import {
insideTelegram,
onTelegramPath,
telegramColorScheme,
+ telegramContentSafeAreaTop,
telegramDisableVerticalSwipes,
telegramHaptic,
telegramLaunch,
@@ -227,6 +228,19 @@ function syncTelegramChrome(): void {
);
}
+/**
+ * syncTelegramSafeArea mirrors Telegram's content-safe-area top inset (the height its native
+ * nav overlays the viewport in fullscreen) into the --tg-content-top CSS var and toggles a
+ * `tg-fullscreen` class, so the header can drop below the nav and lift the menu into its
+ * band (Stage 17). Called on launch and on Telegram's safe-area / fullscreen change events.
+ */
+function syncTelegramSafeArea(): void {
+ if (typeof document === 'undefined') return;
+ const top = telegramContentSafeAreaTop();
+ document.documentElement.style.setProperty('--tg-content-top', `${top}px`);
+ document.documentElement.classList.toggle('tg-fullscreen', top > 0);
+}
+
export async function bootstrap(): Promise
{
const prefs = await loadPrefs();
app.theme = prefs.theme ?? 'auto';
@@ -263,6 +277,9 @@ export async function bootstrap(): Promise {
// Match Telegram's chrome to the app and stop its swipe-down-to-minimise from
// fighting tile drag / board scroll.
syncTelegramChrome();
+ syncTelegramSafeArea();
+ telegramOnEvent('contentSafeAreaChanged', syncTelegramSafeArea);
+ telegramOnEvent('fullscreenChanged', syncTelegramSafeArea);
telegramDisableVerticalSwipes();
try {
await adoptSession(await gateway.authTelegram(launch.initData));
diff --git a/ui/src/lib/telegram.ts b/ui/src/lib/telegram.ts
index 1ed3a63..b7460d7 100644
--- a/ui/src/lib/telegram.ts
+++ b/ui/src/lib/telegram.ts
@@ -10,6 +10,8 @@ interface TelegramWebApp {
initDataUnsafe?: { start_param?: string };
themeParams?: TelegramThemeParams;
colorScheme?: 'light' | 'dark';
+ isFullscreen?: boolean;
+ contentSafeAreaInset?: { top: number; bottom: number; left: number; right: number };
ready?: () => void;
expand?: () => void;
onEvent?: (event: string, handler: () => void) => void;
@@ -99,6 +101,15 @@ export function telegramSetChrome(header: string, background: string, bottom: st
if (bottom) w?.setBottomBarColor?.(bottom);
}
+/**
+ * telegramContentSafeAreaTop returns the height (px) Telegram's own UI overlays at the top of
+ * the viewport in fullscreen (its nav band; the content-safe area, Bot API 8.0). It is 0
+ * outside Telegram or on clients predating it, so callers can pad/position defensively.
+ */
+export function telegramContentSafeAreaTop(): number {
+ return webApp()?.contentSafeAreaInset?.top ?? 0;
+}
+
/**
* telegramDisableVerticalSwipes turns off Telegram's swipe-down-to-minimise gesture so
* it does not fight tile drag-and-drop or the board's vertical scroll.