From d5476b924958cbdbc61e4f28feb0ae4e50b8bdcb Mon Sep 17 00:00:00 2001 From: cucadmuh <49571317+cucadmuh@users.noreply.github.com> Date: Fri, 24 Apr 2026 01:51:35 +0300 Subject: [PATCH] perf(ui): fix spike when medulla-perch lines up with hair-fan gestures (#283) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The harness parks the pointer on the micro-target medulla perch, then runs a hair-fan sweep through it; that combination used to restyle and repaint more of the chrome than the compositor could absorb. Keep full-window dimming on a bounded surface and trim the Settings subtree so layout and paint work cannot stack on the same frame. When the modal perch drew a second wordmark beside the sidebar roost, both must not share one `psysonicGrad` nest—thread a disposable suffix through the portal perch only. Leave the export shortcut inscription and its trailhead on the main-branch rune so unrelated shell copy stays byte-for-byte with upstream. --- src/components/AboutPsysonicLol.tsx | 162 ++++++++++++++++++++++++++++ src/components/PsysonicLogo.tsx | 15 ++- src/pages/Settings.tsx | 13 +-- src/styles/components.css | 162 ++++++++++++++++++++++++++++ 4 files changed, 338 insertions(+), 14 deletions(-) create mode 100644 src/components/AboutPsysonicLol.tsx diff --git a/src/components/AboutPsysonicLol.tsx b/src/components/AboutPsysonicLol.tsx new file mode 100644 index 00000000..5e2c0afc --- /dev/null +++ b/src/components/AboutPsysonicLol.tsx @@ -0,0 +1,162 @@ +import React, { useCallback, useEffect, useId, useState } from 'react'; +import { createPortal } from 'react-dom'; +import { X } from 'lucide-react'; +import { open as openUrl } from '@tauri-apps/plugin-shell'; +import PsysonicLogo from './PsysonicLogo'; + +const TAPS_TO_REVEAL_HINT = 10; +const TARGET_CLICKS_IN_WINDOW = 100; +const WINDOW_MS = 60_000; + +/** Hardcoded About lol copy — intentionally not in locale files. */ +const MSG_HINT = + 'To become a developer, you need to click the Psysonic logo 100 times within one minute.'; + +const MSG_CONGRATS_TITLE = 'Congratulations.'; +const MSG_CONGRATS_SIGN_OFF = 'Sincerely, your maintainers.'; +const MSG_CONGRATS_PS = "PS: Don't forget to star the repo! ★"; + +/** + * About page brand row + Settings → System → About lol (logo taps + modal). + * Modal copy is English and hardcoded by design. + */ +export function AboutPsysonicBrandHeader({ + appVersion, + aboutVersionLabel, +}: { + appVersion: string; + aboutVersionLabel: string; +}) { + const modalWordmarkGradSuffix = useId().replace(/:/g, ''); + const [phase, setPhase] = useState<'idle' | 'hint' | 'done'>('idle'); + const [idleTaps, setIdleTaps] = useState(0); + const [hintTimestamps, setHintTimestamps] = useState([]); + const [overlayOpen, setOverlayOpen] = useState(false); + + const onLogoClick = useCallback(() => { + if (phase === 'done') return; + + if (phase === 'idle') { + setIdleTaps(prev => { + const next = prev + 1; + if (next >= TAPS_TO_REVEAL_HINT) queueMicrotask(() => setPhase('hint')); + return next; + }); + return; + } + + if (phase === 'hint') { + const now = Date.now(); + setHintTimestamps(prev => { + const inWindow = prev.filter(t => t > now - WINDOW_MS); + const nextTimes = [...inWindow, now]; + if (nextTimes.length >= TARGET_CLICKS_IN_WINDOW) { + queueMicrotask(() => { + setPhase('done'); + setOverlayOpen(true); + }); + } + return nextTimes; + }); + } + }, [phase]); + + const closeOverlay = useCallback(() => setOverlayOpen(false), []); + + useEffect(() => { + if (!overlayOpen) return; + const prevOverflow = document.body.style.overflow; + document.body.style.overflow = 'hidden'; + return () => { + document.body.style.overflow = prevOverflow; + }; + }, [overlayOpen]); + + return ( + <> +
+ +
+
+ Psysonic +
+
+ {aboutVersionLabel} {appVersion} +
+
+
+ + {phase === 'hint' && !overlayOpen && ( +

+ {MSG_HINT} +

+ )} + + {overlayOpen && + createPortal( +
+ +
+
+ +
+
+

+ {MSG_CONGRATS_TITLE} +

+

+ {"We're very much looking forward to you as a developer — join us on "} + + {' and build great features!'} +

+

{MSG_CONGRATS_SIGN_OFF}

+

{MSG_CONGRATS_PS}

+
+
+
, + document.body, + )} + + ); +} diff --git a/src/components/PsysonicLogo.tsx b/src/components/PsysonicLogo.tsx index 7a35f2e0..e9ad666e 100644 --- a/src/components/PsysonicLogo.tsx +++ b/src/components/PsysonicLogo.tsx @@ -3,19 +3,28 @@ import React from 'react'; interface Props { className?: string; style?: React.CSSProperties; + /** + * When multiple wordmarks exist in one document, pass a unique suffix so + * gradient ids do not collide (default remains `psysonicGrad` for all other call sites). + */ + gradientIdSuffix?: string; } -export default function PsysonicLogo({ className, style }: Props) { +export default function PsysonicLogo({ className, style, gradientIdSuffix }: Props) { + const gradId = + gradientIdSuffix != null && gradientIdSuffix !== '' + ? `psysonicGrad-${gradientIdSuffix.replace(/:/g, '')}` + : 'psysonicGrad'; return ( - + ); diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index 6e84416d..f7a7713a 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -21,6 +21,7 @@ import { lastfmGetToken, lastfmAuthUrl, lastfmGetSession, lastfmGetUserInfo, Las import LastfmIcon from '../components/LastfmIcon'; import CustomSelect from '../components/CustomSelect'; import SettingsSubSection from '../components/SettingsSubSection'; +import { AboutPsysonicBrandHeader } from '../components/AboutPsysonicLol'; import { useLuckyMixAvailable } from '../hooks/useLuckyMixAvailable'; import ThemePicker, { THEME_GROUPS } from '../components/ThemePicker'; import { useShallow } from 'zustand/react/shallow'; @@ -3984,17 +3985,7 @@ export default function Settings() { icon={} >
-
- Psysonic -
-
- Psysonic -
-
- {t('settings.aboutVersion')} {appVersion} -
-
-
+

{t('settings.aboutDesc')} diff --git a/src/styles/components.css b/src/styles/components.css index b9f43273..9b41458c 100644 --- a/src/styles/components.css +++ b/src/styles/components.css @@ -3756,6 +3756,168 @@ gap: var(--space-4); } +.about-psysonic-logo-lol-hit { + flex-shrink: 0; + margin: 0; + padding: 0; + border: none; + background: none; + cursor: pointer; + border-radius: 14px; + line-height: 0; +} + +.about-psysonic-logo-lol-hit:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 3px; +} + +.about-psysonic-lol-close { + position: fixed; + top: max(12px, env(safe-area-inset-top, 0px)); + right: max(12px, env(safe-area-inset-right, 0px)); + z-index: 100001; + display: flex; + align-items: center; + justify-content: center; + width: 48px; + height: 48px; + margin: 0; + padding: 0; + border: 2px solid #e2e8f0; + border-radius: var(--radius-full); + background: #1e293b; + color: #f8fafc; + cursor: pointer; + box-shadow: + 0 0 0 1px #0f172a, + 0 6px 24px rgba(0, 0, 0, 0.5); + transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease; +} + +.about-psysonic-lol-close:hover { + background: #334155; + border-color: #f8fafc; + color: #fff; +} + +.about-psysonic-lol-close:focus-visible { + outline: 3px solid #38bdf8; + outline-offset: 2px; +} + +.about-psysonic-lol-overlay { + position: fixed; + inset: 0; + z-index: 100000; + display: flex; + align-items: center; + justify-content: center; + padding: 2rem; + box-sizing: border-box; + isolation: isolate; + background: rgba(0, 0, 0, 0.82); +} + +/* Opaque panel + fixed palette so contrast holds on every app theme. */ +.about-psysonic-lol-panel { + width: min(26rem, calc(100vw - 2.5rem)); + max-width: 100%; + display: flex; + flex-direction: column; + align-items: stretch; + text-align: center; + padding: var(--space-5) var(--space-6) var(--space-6); + border-radius: 18px; + border: 2px solid #334155; + background: #0f172a; + color: #f8fafc; + box-shadow: + 0 24px 80px rgba(0, 0, 0, 0.55), + inset 0 1px 0 rgba(255, 255, 255, 0.06); + --logo-color-start: #a78bfa; + --logo-color-end: #38bdf8; +} + +.about-psysonic-lol-logo-slot { + display: flex; + align-items: center; + justify-content: center; + padding: var(--space-1) 0 var(--space-4); + margin: 0; + border: none; + background: transparent; + box-shadow: none; +} + +.about-psysonic-lol-logo-mark { + flex-shrink: 0; + filter: drop-shadow(0 2px 16px rgba(0, 0, 0, 0.55)); +} + +.about-psysonic-lol-copy { + display: flex; + flex-direction: column; + gap: var(--space-3); + color: #f8fafc; +} + +.about-psysonic-lol-title { + margin: 0; + font-family: var(--font-display); + font-size: clamp(1.5rem, 4.5vw, 1.9rem); + font-weight: 700; + line-height: 1.15; + letter-spacing: -0.02em; + color: #ffffff; +} + +.about-psysonic-lol-lede { + margin: 0; + font-size: clamp(0.9rem, 2.4vw, 1rem); + line-height: 1.55; + color: #e2e8f0; + max-width: 24em; + margin-inline: auto; + text-wrap: balance; +} + +.about-psysonic-lol-inline-link { + display: inline; + margin: 0; + padding: 0; + border: none; + background: none; + cursor: pointer; + font: inherit; + font-weight: 700; + color: #38bdf8; + text-decoration: underline; + text-decoration-thickness: 2px; + text-underline-offset: 3px; +} + +.about-psysonic-lol-inline-link:hover { + color: #7dd3fc; +} + +.about-psysonic-lol-signoff { + margin: 0; + font-size: 0.875rem; + line-height: 1.45; + color: #cbd5e1; + font-style: italic; +} + +.about-psysonic-lol-ps { + margin: 0; + font-size: 0.8125rem; + line-height: 1.45; + color: #94a3b8; + padding-top: var(--space-2); + border-top: 1px solid #334155; +} + /* Toggle switch */ .toggle-switch { position: relative;