mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
Merge branch 'main' into exp/orbit
# Conflicts: # src/components/QueuePanel.tsx
This commit is contained in:
@@ -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<number[]>([]);
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<div className="settings-about-header">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onLogoClick}
|
||||||
|
className="about-psysonic-logo-lol-hit"
|
||||||
|
aria-label="Psysonic"
|
||||||
|
>
|
||||||
|
<img src="/logo-psysonic.png" width={52} height={52} alt="" decoding="async" style={{ borderRadius: 14, display: 'block' }} />
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontFamily: 'var(--font-display)', fontSize: '1.25rem', fontWeight: 700, color: 'var(--text-primary)' }}>
|
||||||
|
Psysonic
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 2 }}>
|
||||||
|
{aboutVersionLabel} {appVersion}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{phase === 'hint' && !overlayOpen && (
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
color: 'var(--text-secondary)',
|
||||||
|
marginTop: '0.5rem',
|
||||||
|
lineHeight: 1.5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{MSG_HINT}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{overlayOpen &&
|
||||||
|
createPortal(
|
||||||
|
<div
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-labelledby="about-psysonic-lol-title"
|
||||||
|
className="about-psysonic-lol-overlay"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="about-psysonic-lol-close"
|
||||||
|
aria-label="Close"
|
||||||
|
onClick={closeOverlay}
|
||||||
|
>
|
||||||
|
<X size={26} strokeWidth={2.25} aria-hidden />
|
||||||
|
</button>
|
||||||
|
<div className="about-psysonic-lol-panel">
|
||||||
|
<div className="about-psysonic-lol-logo-slot">
|
||||||
|
<PsysonicLogo
|
||||||
|
gradientIdSuffix={modalWordmarkGradSuffix}
|
||||||
|
className="about-psysonic-lol-logo-mark"
|
||||||
|
style={{
|
||||||
|
height: 'clamp(3.25rem, 14vw, 5.75rem)',
|
||||||
|
width: 'auto',
|
||||||
|
maxWidth: 'min(100%, 420px)',
|
||||||
|
display: 'block',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="about-psysonic-lol-copy">
|
||||||
|
<h2 id="about-psysonic-lol-title" className="about-psysonic-lol-title">
|
||||||
|
{MSG_CONGRATS_TITLE}
|
||||||
|
</h2>
|
||||||
|
<p className="about-psysonic-lol-lede">
|
||||||
|
{"We're very much looking forward to you as a developer — join us on "}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="about-psysonic-lol-inline-link"
|
||||||
|
onClick={() => void openUrl('https://github.com/Psychotoxical/psysonic')}
|
||||||
|
>
|
||||||
|
GitHub
|
||||||
|
</button>
|
||||||
|
{' and build great features!'}
|
||||||
|
</p>
|
||||||
|
<p className="about-psysonic-lol-signoff">{MSG_CONGRATS_SIGN_OFF}</p>
|
||||||
|
<p className="about-psysonic-lol-ps">{MSG_CONGRATS_PS}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
document.body,
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import { createPortal } from 'react-dom';
|
|||||||
import { emit, listen } from '@tauri-apps/api/event';
|
import { emit, listen } from '@tauri-apps/api/event';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Play, Pause, SkipBack, SkipForward, Pin, PinOff, Maximize2, X, ListMusic, Volume2, VolumeX, Shuffle, Infinity as InfinityIcon, Waves, ArrowUpToLine } from 'lucide-react';
|
import { Play, Pause, SkipBack, SkipForward, Pin, PinOff, Maximize2, X, ListMusic, Volume2, VolumeX, Shuffle, Infinity as InfinityIcon, Waves, MoveRight } from 'lucide-react';
|
||||||
import CachedImage from './CachedImage';
|
import CachedImage from './CachedImage';
|
||||||
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic';
|
import { buildCoverArtUrl, coverArtCacheKey } from '../api/subsonic';
|
||||||
import { usePlayerStore } from '../store/playerStore';
|
import { usePlayerStore } from '../store/playerStore';
|
||||||
@@ -582,7 +582,7 @@ export default function MiniPlayer() {
|
|||||||
data-tooltip={t('queue.gapless')}
|
data-tooltip={t('queue.gapless')}
|
||||||
aria-label={t('queue.gapless')}
|
aria-label={t('queue.gapless')}
|
||||||
>
|
>
|
||||||
<InfinityIcon size={13} />
|
<MoveRight size={13} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -604,7 +604,7 @@ export default function MiniPlayer() {
|
|||||||
data-tooltip={t('queue.infiniteQueue')}
|
data-tooltip={t('queue.infiniteQueue')}
|
||||||
aria-label={t('queue.infiniteQueue')}
|
aria-label={t('queue.infiniteQueue')}
|
||||||
>
|
>
|
||||||
<ArrowUpToLine size={13} />
|
<InfinityIcon size={13} />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<span className="mini-player__toolbar-sep" aria-hidden />
|
<span className="mini-player__toolbar-sep" aria-hidden />
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@ import React, { useState, useRef, useMemo, useEffect } from 'react';
|
|||||||
import { Track, usePlayerStore, songToTrack } from '../store/playerStore';
|
import { Track, usePlayerStore, songToTrack } from '../store/playerStore';
|
||||||
import { useOrbitStore } from '../store/orbitStore';
|
import { useOrbitStore } from '../store/orbitStore';
|
||||||
import OrbitGuestQueue from './OrbitGuestQueue';
|
import OrbitGuestQueue from './OrbitGuestQueue';
|
||||||
import { Play, Music, Star, X, Trash2, Save, FolderOpen, Shuffle, Infinity, Waves, MicVocal, ListMusic, Check, ListPlus, ArrowUpToLine, Radio, HardDrive, ChevronDown, Info, Share2 } from 'lucide-react';
|
import { Play, Music, Star, X, Trash2, Save, FolderOpen, Shuffle, Infinity, Waves, MicVocal, ListMusic, Check, ListPlus, MoveRight, Radio, HardDrive, ChevronDown, Info, Share2 } from 'lucide-react';
|
||||||
import { buildCoverArtUrl, coverArtCacheKey, getAlbum, getPlaylists, getPlaylist, updatePlaylist, deletePlaylist, SubsonicPlaylist } from '../api/subsonic';
|
import { buildCoverArtUrl, coverArtCacheKey, getAlbum, getPlaylists, getPlaylist, updatePlaylist, deletePlaylist, SubsonicPlaylist } from '../api/subsonic';
|
||||||
import { usePlaylistStore } from '../store/playlistStore';
|
import { usePlaylistStore } from '../store/playlistStore';
|
||||||
import { useCachedUrl } from './CachedImage';
|
import { useCachedUrl } from './CachedImage';
|
||||||
@@ -602,7 +602,7 @@ function QueuePanelHostOrSolo() {
|
|||||||
data-tooltip={t('queue.gapless')}
|
data-tooltip={t('queue.gapless')}
|
||||||
aria-label={t('queue.gapless')}
|
aria-label={t('queue.gapless')}
|
||||||
>
|
>
|
||||||
<Infinity size={13} />
|
<MoveRight size={13} />
|
||||||
</button>
|
</button>
|
||||||
<div style={{ position: 'relative' }}>
|
<div style={{ position: 'relative' }}>
|
||||||
<button
|
<button
|
||||||
@@ -654,7 +654,7 @@ function QueuePanelHostOrSolo() {
|
|||||||
data-tooltip={t('queue.infiniteQueue')}
|
data-tooltip={t('queue.infiniteQueue')}
|
||||||
aria-label={t('queue.infiniteQueue')}
|
aria-label={t('queue.infiniteQueue')}
|
||||||
>
|
>
|
||||||
<ArrowUpToLine size={13} />
|
<Infinity size={13} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+18
-11
@@ -21,6 +21,7 @@ import { lastfmGetToken, lastfmAuthUrl, lastfmGetSession, lastfmGetUserInfo, Las
|
|||||||
import LastfmIcon from '../components/LastfmIcon';
|
import LastfmIcon from '../components/LastfmIcon';
|
||||||
import CustomSelect from '../components/CustomSelect';
|
import CustomSelect from '../components/CustomSelect';
|
||||||
import SettingsSubSection from '../components/SettingsSubSection';
|
import SettingsSubSection from '../components/SettingsSubSection';
|
||||||
|
import { AboutPsysonicBrandHeader } from '../components/AboutPsysonicLol';
|
||||||
import { useLuckyMixAvailable } from '../hooks/useLuckyMixAvailable';
|
import { useLuckyMixAvailable } from '../hooks/useLuckyMixAvailable';
|
||||||
import ThemePicker, { THEME_GROUPS } from '../components/ThemePicker';
|
import ThemePicker, { THEME_GROUPS } from '../components/ThemePicker';
|
||||||
import { useShallow } from 'zustand/react/shallow';
|
import { useShallow } from 'zustand/react/shallow';
|
||||||
@@ -142,6 +143,13 @@ const CONTRIBUTORS = [
|
|||||||
'WebKitGTK wheel scroll mode: smooth (kinetic) default with optional linear toggle (PR #207)',
|
'WebKitGTK wheel scroll mode: smooth (kinetic) default with optional linear toggle (PR #207)',
|
||||||
'ArtistCardLocal i18n: use plural-aware artists.albumCount key instead of hardcoded German',
|
'ArtistCardLocal i18n: use plural-aware artists.albumCount key instead of hardcoded German',
|
||||||
'NixOS / flake install guide with Cachix setup (PR #209)',
|
'NixOS / flake install guide with Cachix setup (PR #209)',
|
||||||
|
'Subsonic: align Rust HTTP UA with main WebView UA (PR #235)',
|
||||||
|
'UI: overlay scrollbars, resizer hit-test, and Linux mini-wheel (PR #255)',
|
||||||
|
'Server invites: magic-string paste, Navidrome admin share, add-user validation (PR #258)',
|
||||||
|
'Linux: stop Wayland GTK drag proxy and PsyDnD ghost (PR #268)',
|
||||||
|
'Sidebar: long-press drag to reorder and hide nav items (PR #269)',
|
||||||
|
'Sleep timer and delayed start via long-press on play/pause (PR #270)',
|
||||||
|
'Lucky Mix — instant queue from listening history, ratings, and AudioMuse similar tracks (PR #278)',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -183,6 +191,8 @@ const CONTRIBUTORS = [
|
|||||||
'Click-to-toggle duration / remaining time in player bar with persisted preference (PR #212)',
|
'Click-to-toggle duration / remaining time in player bar with persisted preference (PR #212)',
|
||||||
'Opt-in floating player bar with themed background, accent-colored border, rounded album art, and centered volume section (PR #216)',
|
'Opt-in floating player bar with themed background, accent-colored border, rounded album art, and centered volume section (PR #216)',
|
||||||
'Linux GPU-vendor auto-detection to configure the WebKitGTK DMA-BUF renderer (disabled on NVIDIA proprietary) (PR #217)',
|
'Linux GPU-vendor auto-detection to configure the WebKitGTK DMA-BUF renderer (disabled on NVIDIA proprietary) (PR #217)',
|
||||||
|
'Artist page: continue playback when starting top songs (PR #220)',
|
||||||
|
'Floating player bar: scroll-padding fix (PR #221)',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -194,6 +204,13 @@ const CONTRIBUTORS = [
|
|||||||
'ICY stream metadata & AzuraCast Now Playing support (PR #146)',
|
'ICY stream metadata & AzuraCast Now Playing support (PR #146)',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
github: 'peri4ko',
|
||||||
|
since: '1.43.0',
|
||||||
|
contributions: [
|
||||||
|
'WebView2 idle hooks when Tauri windows are hidden — Windows GPU and compositor mitigation (PR #273)',
|
||||||
|
],
|
||||||
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const MAINTAINERS = [
|
const MAINTAINERS = [
|
||||||
@@ -3984,17 +4001,7 @@ export default function Settings() {
|
|||||||
icon={<Info size={16} />}
|
icon={<Info size={16} />}
|
||||||
>
|
>
|
||||||
<div className="settings-card settings-about">
|
<div className="settings-card settings-about">
|
||||||
<div className="settings-about-header">
|
<AboutPsysonicBrandHeader appVersion={appVersion} aboutVersionLabel={t('settings.aboutVersion')} />
|
||||||
<img src="/logo-psysonic.png" width={52} height={52} alt="Psysonic" style={{ borderRadius: 14 }} />
|
|
||||||
<div>
|
|
||||||
<div style={{ fontFamily: 'var(--font-display)', fontSize: '1.25rem', fontWeight: 700, color: 'var(--text-primary)' }}>
|
|
||||||
Psysonic
|
|
||||||
</div>
|
|
||||||
<div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 2 }}>
|
|
||||||
{t('settings.aboutVersion')} {appVersion}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p style={{ fontSize: 13, color: 'var(--text-secondary)', lineHeight: 1.6, margin: '1rem 0 0.5rem' }}>
|
<p style={{ fontSize: 13, color: 'var(--text-secondary)', lineHeight: 1.6, margin: '1rem 0 0.5rem' }}>
|
||||||
{t('settings.aboutDesc')}
|
{t('settings.aboutDesc')}
|
||||||
|
|||||||
@@ -3756,6 +3756,168 @@
|
|||||||
gap: var(--space-4);
|
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 */
|
||||||
.toggle-switch {
|
.toggle-switch {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
Reference in New Issue
Block a user