animation tweaks and mobile desktop fixes
This commit is contained in:
parent
8a379ec4b5
commit
ee11b4f71b
4 changed files with 107 additions and 16 deletions
|
|
@ -1,8 +1,21 @@
|
||||||
|
const USERNAME = "dvorakmortonxd";
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch("https://git.dvorakmortonxd.dev/api/v1/users/dvorakmortonxd/activities/feeds?limit=20");
|
// The public user feed also contains activity from repositories the user
|
||||||
|
// watches (e.g. commits pushed by other people to an org repo), so pull a
|
||||||
|
// larger page and keep only actions the user actually performed.
|
||||||
|
const res = await fetch(
|
||||||
|
`https://git.dvorakmortonxd.dev/api/v1/users/${USERNAME}/activities/feeds?limit=50`
|
||||||
|
);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
return Response.json(data);
|
if (!Array.isArray(data)) {
|
||||||
|
return Response.json(data);
|
||||||
|
}
|
||||||
|
const mine = data.filter(
|
||||||
|
(act) => act?.act_user?.login === USERNAME
|
||||||
|
);
|
||||||
|
return Response.json(mine);
|
||||||
} catch {
|
} catch {
|
||||||
return Response.json({ error: "failed to load" }, { status: 500 });
|
return Response.json({ error: "failed to load" }, { status: 500 });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,11 @@ export default function ServiceButton({ url, checkUrl, className, icon, name, de
|
||||||
const statusColor = effectiveStatus === 'up' ? '#4ade80' : effectiveStatus === 'down' ? '#ef4444' : '#9ca3af';
|
const statusColor = effectiveStatus === 'up' ? '#4ade80' : effectiveStatus === 'down' ? '#ef4444' : '#9ca3af';
|
||||||
const statusGlow = effectiveStatus === 'up' ? 'rgba(74, 222, 128, 0.9)' : effectiveStatus === 'down' ? 'rgba(239, 68, 68, 0.9)' : 'transparent';
|
const statusGlow = effectiveStatus === 'up' ? 'rgba(74, 222, 128, 0.9)' : effectiveStatus === 'down' ? 'rgba(239, 68, 68, 0.9)' : 'transparent';
|
||||||
const statusLabel = effectiveStatus === 'up' ? 'Active' : effectiveStatus === 'down' ? 'Dead' : 'Checking...';
|
const statusLabel = effectiveStatus === 'up' ? 'Active' : effectiveStatus === 'down' ? 'Dead' : 'Checking...';
|
||||||
const isFriend = className === 'friend';
|
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
<>
|
<>
|
||||||
<span className="service-icon-wrap">
|
<span className="service-icon-wrap">
|
||||||
{isFriend && <span className="service-status-dot" />}
|
<span className="service-status-dot" />
|
||||||
<span className="service-icon">{icon}</span>
|
<span className="service-icon">{icon}</span>
|
||||||
</span>
|
</span>
|
||||||
<span className="service-text">
|
<span className="service-text">
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ export default function WindowsDesktop() {
|
||||||
]);
|
]);
|
||||||
const [activeId, setActiveId] = useState<number | null>(1);
|
const [activeId, setActiveId] = useState<number | null>(1);
|
||||||
const [startOpen, setStartOpen] = useState(false);
|
const [startOpen, setStartOpen] = useState(false);
|
||||||
|
const [startClosing, setStartClosing] = useState(false);
|
||||||
const [selectedIcon, setSelectedIcon] = useState<string | null>(null);
|
const [selectedIcon, setSelectedIcon] = useState<string | null>(null);
|
||||||
const [closingIds, setClosingIds] = useState<number[]>([]);
|
const [closingIds, setClosingIds] = useState<number[]>([]);
|
||||||
|
|
||||||
|
|
@ -78,7 +79,7 @@ export default function WindowsDesktop() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const openApp = (appId: string) => {
|
const openApp = (appId: string) => {
|
||||||
setStartOpen(false);
|
if (startOpen) setStartClosing(true);
|
||||||
const existing = wins.find((w) => w.appId === appId);
|
const existing = wins.find((w) => w.appId === appId);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
setWins((prev) =>
|
setWins((prev) =>
|
||||||
|
|
@ -117,6 +118,21 @@ export default function WindowsDesktop() {
|
||||||
setActiveId(id);
|
setActiveId(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const closeStart = () => {
|
||||||
|
if (startOpen && !startClosing) setStartClosing(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleStart = () => {
|
||||||
|
if (!startOpen) {
|
||||||
|
setStartClosing(false);
|
||||||
|
setStartOpen(true);
|
||||||
|
} else if (startClosing) {
|
||||||
|
setStartClosing(false);
|
||||||
|
} else {
|
||||||
|
setStartClosing(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Animate the window out first, then drop it from state.
|
// Animate the window out first, then drop it from state.
|
||||||
const closeWin = (id: number) =>
|
const closeWin = (id: number) =>
|
||||||
setClosingIds((prev) => (prev.includes(id) ? prev : [...prev, id]));
|
setClosingIds((prev) => (prev.includes(id) ? prev : [...prev, id]));
|
||||||
|
|
@ -168,7 +184,7 @@ export default function WindowsDesktop() {
|
||||||
style={{ backgroundImage: wallpaper }}
|
style={{ backgroundImage: wallpaper }}
|
||||||
onPointerDown={(e) => {
|
onPointerDown={(e) => {
|
||||||
if (e.target === e.currentTarget) {
|
if (e.target === e.currentTarget) {
|
||||||
setStartOpen(false);
|
closeStart();
|
||||||
setSelectedIcon(null);
|
setSelectedIcon(null);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
@ -264,7 +280,17 @@ export default function WindowsDesktop() {
|
||||||
|
|
||||||
{/* Application launcher */}
|
{/* Application launcher */}
|
||||||
{startOpen && (
|
{startOpen && (
|
||||||
<div className="kickoff" onPointerDown={(e) => e.stopPropagation()}>
|
<div
|
||||||
|
className={"kickoff" + (startClosing ? " closing" : "")}
|
||||||
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
|
onAnimationEnd={(e) => {
|
||||||
|
if (e.target !== e.currentTarget) return;
|
||||||
|
if (startClosing) {
|
||||||
|
setStartOpen(false);
|
||||||
|
setStartClosing(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div className="kickoff-content">
|
<div className="kickoff-content">
|
||||||
<div className="kickoff-section-title">Applications</div>
|
<div className="kickoff-section-title">Applications</div>
|
||||||
<div className="kickoff-list">
|
<div className="kickoff-list">
|
||||||
|
|
@ -333,7 +359,7 @@ export default function WindowsDesktop() {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={"win-start" + (startOpen ? " open" : "")}
|
className={"win-start" + (startOpen ? " open" : "")}
|
||||||
onClick={() => setStartOpen((v) => !v)}
|
onClick={() => toggleStart()}
|
||||||
aria-label="Application Launcher"
|
aria-label="Application Launcher"
|
||||||
title="Application Launcher"
|
title="Application Launcher"
|
||||||
>
|
>
|
||||||
|
|
@ -352,7 +378,8 @@ export default function WindowsDesktop() {
|
||||||
className={
|
className={
|
||||||
"win-tb-btn" +
|
"win-tb-btn" +
|
||||||
(activeId === w.id && !w.minimized ? " active" : "") +
|
(activeId === w.id && !w.minimized ? " active" : "") +
|
||||||
(w.minimized ? " minimized" : "")
|
(w.minimized ? " minimized" : "") +
|
||||||
|
(closingIds.includes(w.id) ? " closing" : "")
|
||||||
}
|
}
|
||||||
onClick={() => taskbarClick(w)}
|
onClick={() => taskbarClick(w)}
|
||||||
title={app.title}
|
title={app.title}
|
||||||
|
|
|
||||||
|
|
@ -92,17 +92,13 @@ html[data-device="phone"] .skeuo-mobile-root { display: block; }
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95);
|
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95);
|
||||||
transition: transform 0.12s ease, background-color 0.12s ease,
|
transition: background-color 0.12s ease, border-color 0.12s ease;
|
||||||
border-color 0.12s ease;
|
|
||||||
}
|
}
|
||||||
.win-icon:hover,
|
.win-icon:hover,
|
||||||
.win-icon.selected {
|
.win-icon.selected {
|
||||||
background: rgba(61, 174, 233, 0.16);
|
background: rgba(61, 174, 233, 0.16);
|
||||||
border-color: rgba(61, 174, 233, 0.4);
|
border-color: rgba(61, 174, 233, 0.4);
|
||||||
}
|
}
|
||||||
.win-icon:active {
|
|
||||||
transform: scale(0.9);
|
|
||||||
}
|
|
||||||
.win-icon-glyph {
|
.win-icon-glyph {
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
|
|
@ -128,8 +124,11 @@ a.win-icon {
|
||||||
right: 10px;
|
right: 10px;
|
||||||
left: auto;
|
left: auto;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
flex-wrap: nowrap;
|
/* Wrap into extra columns on short screens so the service shortcuts never
|
||||||
max-height: none;
|
collide with the friends stack anchored to the bottom-right. */
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-content: flex-start;
|
||||||
|
max-height: calc(100% - 350px);
|
||||||
}
|
}
|
||||||
.win-icons-br {
|
.win-icons-br {
|
||||||
top: auto;
|
top: auto;
|
||||||
|
|
@ -396,6 +395,17 @@ a.win-icon {
|
||||||
.app-surface .service-button::before {
|
.app-surface .service-button::before {
|
||||||
content: none;
|
content: none;
|
||||||
}
|
}
|
||||||
|
/* Emoji glyphs get a square drop-shadow, so the status glow is drawn with the
|
||||||
|
status dot behind the icon instead of a filter on the glyph. */
|
||||||
|
.app-surface .service-button:not(.friend) .service-icon {
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
.app-surface .service-button:not(.friend) .service-status-dot {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
background: radial-gradient(circle, var(--status-glow) 0%, rgba(0, 0, 0, 0) 70%);
|
||||||
|
filter: blur(1px);
|
||||||
|
}
|
||||||
.app-surface .service-button:hover {
|
.app-surface .service-button:hover {
|
||||||
background: linear-gradient(to bottom, #f2f9ff, #cfe4f8);
|
background: linear-gradient(to bottom, #f2f9ff, #cfe4f8);
|
||||||
border-color: #7db9e8;
|
border-color: #7db9e8;
|
||||||
|
|
@ -912,6 +922,31 @@ a.win-icon {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #eff0f1;
|
color: #eff0f1;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
animation: tbBtnIn 0.16s ease-out;
|
||||||
|
}
|
||||||
|
.win-tb-btn.closing {
|
||||||
|
animation: tbBtnOut 0.12s ease-in forwards;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
@keyframes tbBtnIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(6px) scale(0.92);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes tbBtnOut {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(6px) scale(0.92);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.win-tb-btn::after {
|
.win-tb-btn::after {
|
||||||
content: "";
|
content: "";
|
||||||
|
|
@ -1031,6 +1066,20 @@ a.win-icon {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
}
|
}
|
||||||
|
.kickoff.closing {
|
||||||
|
animation: kickoffOut 0.16s ease-in forwards;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
@keyframes kickoffOut {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px) scale(0.98);
|
||||||
|
}
|
||||||
|
}
|
||||||
.kickoff-section-title {
|
.kickoff-section-title {
|
||||||
margin: 4px 2px 8px;
|
margin: 4px 2px 8px;
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
|
|
@ -1098,6 +1147,9 @@ a.win-icon {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", Roboto,
|
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", Roboto,
|
||||||
"Helvetica Neue", Arial, sans-serif;
|
"Helvetica Neue", Arial, sans-serif;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-touch-callout: none;
|
||||||
}
|
}
|
||||||
.mobile-home::before {
|
.mobile-home::before {
|
||||||
content: "";
|
content: "";
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue