New pages for scroll

This commit is contained in:
Jordan Eldredge 2025-12-29 17:29:11 -08:00
parent 8d4ff41f42
commit 1fb930cd63
4 changed files with 371 additions and 6 deletions

View file

@ -57,7 +57,7 @@ export default function BottomMenuBar() {
{isHamburgerOpen && (
<div
style={{
position: "absolute",
position: "fixed",
bottom: "4.5rem",
left: "50%",
transform: "translateX(-50%)",
@ -65,13 +65,15 @@ export default function BottomMenuBar() {
maxWidth: MOBILE_MAX_WIDTH,
backgroundColor: "rgba(26, 26, 26, 0.98)",
backdropFilter: "blur(10px)",
borderTop: "1px solid rgba(255, 255, 255, 0.1)",
border: "1px solid rgba(255, 255, 255, 0.2)",
borderBottom: "none",
boxShadow: "0 -4px 12px rgba(0, 0, 0, 0.3)",
zIndex: 999,
}}
>
<div ref={menuRef}>
<HamburgerMenuItem
href="/about"
href="/scroll/about"
icon={<Info size={20} />}
label="About"
onClick={() => {
@ -87,13 +89,12 @@ export default function BottomMenuBar() {
}}
/>{" "}
<HamburgerMenuItem
href="https://github.com/captbaritone/webamp/issues"
href="/scroll/feedback"
icon={<MessageSquare size={20} />}
label="Feedback"
onClick={() => {
setIsHamburgerOpen(false);
}}
external
/>
<HamburgerMenuItem
href="https://github.com/captbaritone/webamp/"
@ -111,7 +112,7 @@ export default function BottomMenuBar() {
{/* Bottom Menu Bar */}
<div
style={{
position: "absolute",
position: "sticky",
bottom: 0,
left: 0,
width: "100%",

View file

@ -0,0 +1,180 @@
import { ReactNode, CSSProperties } from "react";
import { MOBILE_MAX_WIDTH } from "../../../legacy-client/src/constants";
type StaticPageProps = {
children: ReactNode;
};
export default function StaticPage({ children }: StaticPageProps) {
return (
<div
style={{
minHeight: "100vh",
backgroundColor: "#1a1a1a",
paddingBottom: "5rem", // Space for bottom menu bar
boxSizing: "border-box",
}}
>
<div
style={{
maxWidth: MOBILE_MAX_WIDTH,
margin: "0 auto",
padding: "2rem 1.5rem",
color: "#fff",
lineHeight: "1.6",
}}
>
{children}
</div>
</div>
);
}
// Styled heading components
export function Heading({ children }: { children: ReactNode }) {
return (
<h1
style={{
fontSize: "2rem",
marginBottom: "1.5rem",
fontWeight: 600,
}}
>
{children}
</h1>
);
}
export function Subheading({ children }: { children: ReactNode }) {
return (
<h2
style={{
fontSize: "1.5rem",
marginTop: "2rem",
marginBottom: "1rem",
fontWeight: 600,
}}
>
{children}
</h2>
);
}
// Styled link component
export function Link({
href,
children,
...props
}: {
href: string;
children: ReactNode;
target?: string;
rel?: string;
}) {
return (
<a
href={href}
style={{
color: "#6b9eff",
textDecoration: "underline",
}}
{...props}
>
{children}
</a>
);
}
// Styled paragraph component
export function Paragraph({ children }: { children: ReactNode }) {
return <p style={{ marginBottom: "1rem" }}>{children}</p>;
}
// Styled form components
export function Label({ children }: { children: ReactNode }) {
return (
<label
style={{
display: "block",
marginBottom: "0.5rem",
fontWeight: 500,
}}
>
{children}
</label>
);
}
export function Input({
style,
...props
}: React.InputHTMLAttributes<HTMLInputElement> & { style?: CSSProperties }) {
return (
<input
style={{
width: "100%",
padding: "0.75rem",
fontSize: "1rem",
backgroundColor: "#2a2a2a",
border: "1px solid rgba(255, 255, 255, 0.2)",
borderRadius: "4px",
color: "#fff",
fontFamily: "inherit",
...style,
}}
{...props}
/>
);
}
export function Textarea({
style,
...props
}: React.TextareaHTMLAttributes<HTMLTextAreaElement> & {
style?: CSSProperties;
}) {
return (
<textarea
style={{
width: "100%",
padding: "0.75rem",
fontSize: "1rem",
backgroundColor: "#2a2a2a",
border: "1px solid rgba(255, 255, 255, 0.2)",
borderRadius: "4px",
color: "#fff",
fontFamily: "inherit",
display: "block",
resize: "vertical",
...style,
}}
{...props}
/>
);
}
export function Button({
style,
disabled,
...props
}: React.ButtonHTMLAttributes<HTMLButtonElement> & { style?: CSSProperties }) {
return (
<button
style={{
padding: "0.75rem 2rem",
fontSize: "1rem",
fontWeight: 500,
backgroundColor: "#6b9eff",
color: "#fff",
border: "none",
borderRadius: "4px",
cursor: disabled ? "not-allowed" : "pointer",
opacity: disabled ? 0.6 : 1,
transition: "opacity 0.2s",
...style,
}}
disabled={disabled}
{...props}
/>
);
}

View file

@ -0,0 +1,67 @@
import StaticPage, {
Heading,
Subheading,
Link,
Paragraph,
} from "../StaticPage";
export default function AboutPage() {
return (
<StaticPage>
<Heading>About</Heading>
<Paragraph>
The Winamp Skin Museum is an attempt to build a <i>fast</i>,{" "}
<i>searchable</i>, and <i>shareable</i>, interface for the collection of
Winamp Skins amassed on the{" "}
<Link
href="https://archive.org/details/winampskins"
target="_blank"
rel="noopener noreferrer"
>
Internet Archive
</Link>
.
</Paragraph>
<Subheading>Features:</Subheading>
<ul style={{ marginBottom: "1.5rem", paddingLeft: "1.5rem" }}>
<li style={{ marginBottom: "0.5rem" }}>
<strong>Infinite scroll</strong> preview images
</li>
<li style={{ marginBottom: "0.5rem" }}>
<strong>Experience</strong> skins with integrated{" "}
<Link
href="https://webamp.org"
target="_blank"
rel="noopener noreferrer"
>
Webamp
</Link>
</li>
<li style={{ marginBottom: "0.5rem" }}>
<strong>Fast search</strong> of indexed readme.txt texts
</li>
</ul>
<Paragraph>
Made by <Link href="https://jordaneldredge.com">Jordan Eldredge</Link>
</Paragraph>
<hr
style={{
border: "none",
borderTop: "1px solid rgba(255, 255, 255, 0.2)",
margin: "2rem 0",
}}
/>
<Paragraph>
Want Winamp on your Windows PC, but with supported updates & new
features?{" "}
<Link
href="https://getwacup.com/"
target="_blank"
rel="noopener noreferrer"
>
Try WACUP
</Link>
</Paragraph>
</StaticPage>
);
}

View file

@ -0,0 +1,117 @@
"use client";
import { useState, useCallback } from "react";
import { usePathname } from "next/navigation";
import StaticPage, {
Heading,
Paragraph,
Label,
Input,
Textarea,
Button,
} from "../StaticPage";
async function sendFeedback(variables: {
message: string;
email: string;
url: string;
}) {
const mutation = `
mutation GiveFeedback($message: String!, $email: String, $url: String) {
send_feedback(message: $message, email: $email, url: $url)
}
`;
const response = await fetch("/api/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
query: mutation,
variables,
}),
});
if (!response.ok) {
throw new Error("Failed to send feedback");
}
return response.json();
}
export default function FeedbackPage() {
const pathname = usePathname();
const [message, setMessage] = useState("");
const [email, setEmail] = useState("");
const [sending, setSending] = useState(false);
const [sent, setSent] = useState(false);
const send = useCallback(async () => {
if (message.trim().length === 0) {
alert("Please add a message before sending.");
return;
}
const body = {
message,
email,
url: "https://skins.webamp.org" + pathname,
};
setSending(true);
try {
await sendFeedback(body);
setSent(true);
} catch (error) {
alert("Failed to send feedback. Please try again.");
setSending(false);
}
}, [message, email, pathname]);
if (sent) {
return (
<StaticPage>
<Heading>Sent!</Heading>
<Paragraph>
Thanks for your feedback. I appreciate you taking the time to share
your thoughts.
</Paragraph>
</StaticPage>
);
}
return (
<StaticPage>
<Heading>Feedback</Heading>
<p style={{ marginBottom: "1.5rem" }}>
Let me know what you think about the Winamp Skin Museum. Bug reports,
feature suggestions, personal anecdotes, or criticism are all welcome.
</p>
<div style={{ marginBottom: "1.5rem" }}>
<Label>Message</Label>
<Textarea
disabled={sending}
value={message}
onChange={(e) => setMessage(e.target.value)}
style={{ minHeight: 150 }}
placeholder="Your thoughts here..."
/>
</div>
<div style={{ marginBottom: "1.5rem" }}>
<Label>Email (optional)</Label>
<Input
disabled={sending}
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="user@example.com"
/>
</div>
<div style={{ textAlign: "right" }}>
<Button onClick={send} disabled={sending}>
{sending ? "Sending..." : "Send"}
</Button>
</div>
</StaticPage>
);
}