chore: added xp styles d9fbf7b1
Steve · 2026-01-12 23:23 7 file(s) · +134 −176
bun.lock +10 −23
6 6
      "name": "atfeeds",
7 7
    },
8 8
    "packages/client": {
9 -
      "name": "@atfeeds/client",
9 +
      "name": "@document-feeds/client",
10 10
      "version": "1.0.0",
11 11
      "dependencies": {
12 12
        "react": "^18.2.0",
13 13
        "react-dom": "^18.2.0",
14 +
        "xp.css": "^0.2.6",
14 15
      },
15 16
      "devDependencies": {
16 17
        "@types/react": "^18.2.0",
20 21
        "vite": "^5.0.0",
21 22
      },
22 23
    },
23 -
    "packages/cloudflare": {
24 -
      "name": "atfeeds-cloudflare",
24 +
    "packages/server": {
25 +
      "name": "@document-feeds/server",
25 26
      "version": "1.0.0",
26 27
      "dependencies": {
27 28
        "hono": "^4.0.0",
29 30
      "devDependencies": {
30 31
        "@cloudflare/workers-types": "^4.20240117.0",
31 32
        "wrangler": "^3.0.0",
32 -
      },
33 -
    },
34 -
    "packages/server": {
35 -
      "name": "@atfeeds/server",
36 -
      "version": "1.0.0",
37 -
      "dependencies": {
38 -
        "hono": "^4.0.0",
39 -
      },
40 -
      "devDependencies": {
41 -
        "@types/bun": "^1.0.0",
42 33
      },
43 34
    },
44 35
  },
45 36
  "packages": {
46 -
    "@atfeeds/client": ["@atfeeds/client@workspace:packages/client"],
47 -
48 -
    "@atfeeds/server": ["@atfeeds/server@workspace:packages/server"],
49 -
50 37
    "@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="],
51 38
52 39
    "@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="],
102 89
    "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20260111.0", "", {}, "sha512-NFA2U+AqEWHkAmw6oRzNWJyc14rIvBlF/OlK3lixokunRKwyziuON07nWUZ0w0kKWlW4fJ/muA09tEUaQY07tA=="],
103 90
104 91
    "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
92 +
93 +
    "@document-feeds/client": ["@document-feeds/client@workspace:packages/client"],
94 +
95 +
    "@document-feeds/server": ["@document-feeds/server@workspace:packages/server"],
105 96
106 97
    "@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
107 98
265 256
266 257
    "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
267 258
268 -
    "@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
269 -
270 259
    "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
271 260
272 261
    "@types/node": ["@types/node@25.0.6", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-NNu0sjyNxpoiW3YuVFfNz7mxSQ+S4X2G28uqg2s+CzoqoQjLPsWSbsFFyztIAqt2vb8kfEAsJNepMGPTxFDx3Q=="],
285 274
286 275
    "as-table": ["as-table@1.0.55", "", { "dependencies": { "printable-characters": "^1.0.42" } }, "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ=="],
287 276
288 -
    "atfeeds-cloudflare": ["atfeeds-cloudflare@workspace:packages/cloudflare"],
289 -
290 277
    "baseline-browser-mapping": ["baseline-browser-mapping@2.9.14", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg=="],
291 278
292 279
    "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="],
293 280
294 281
    "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
295 -
296 -
    "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
297 282
298 283
    "caniuse-lite": ["caniuse-lite@1.0.30001764", "", {}, "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g=="],
299 284
434 419
    "wrangler": ["wrangler@3.114.16", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.3.4", "@cloudflare/unenv-preset": "2.0.2", "@esbuild-plugins/node-globals-polyfill": "0.2.3", "@esbuild-plugins/node-modules-polyfill": "0.2.2", "blake3-wasm": "2.1.5", "esbuild": "0.17.19", "miniflare": "3.20250718.3", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.14", "workerd": "1.20250718.0" }, "optionalDependencies": { "fsevents": "~2.3.2", "sharp": "^0.33.5" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250408.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-ve/ULRjrquu5BHNJ+1T0ipJJlJ6pD7qLmhwRkk0BsUIxatNe4HP4odX/R4Mq/RHG6LOnVAFs7SMeSHlz/1mNlQ=="],
435 420
436 421
    "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
422 +
423 +
    "xp.css": ["xp.css@0.2.6", "", {}, "sha512-v0qUB4wOvyXIMLNid5bIpm+0n5aL4i8wE5dqJH8LNVxE04PxUUxjwf2rNzengegUIK03XC4vbfLVCPAfMLtuqA=="],
437 424
438 425
    "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
439 426
packages/client/index.html +1 −1
3 3
  <head>
4 4
    <meta charset="UTF-8" />
5 5
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 -
    <title>AT Feeds - Standard.site Documents</title>
6 +
    <title>docs.surf</title>
7 7
  </head>
8 8
  <body>
9 9
    <div id="root"></div>
packages/client/package.json +2 −1
10 10
	},
11 11
	"dependencies": {
12 12
		"react": "^18.2.0",
13 -
		"react-dom": "^18.2.0"
13 +
		"react-dom": "^18.2.0",
14 +
		"xp.css": "^0.2.6"
14 15
	},
15 16
	"devDependencies": {
16 17
		"@types/react": "^18.2.0",
packages/client/src/App.tsx +109 −64
31 31
	const [loading, setLoading] = useState(true);
32 32
	const [error, setError] = useState<string | null>(null);
33 33
34 -
	useEffect(() => {
35 -
		async function fetchFeed() {
36 -
			try {
37 -
				const response = await fetch(`${API_URL}/feed`);
38 -
				if (!response.ok) {
39 -
					throw new Error("Failed to fetch feed");
40 -
				}
41 -
				const data: FeedResponse = await response.json();
42 -
				setDocuments(data.documents);
43 -
			} catch (err) {
44 -
				setError(err instanceof Error ? err.message : "Unknown error");
45 -
			} finally {
46 -
				setLoading(false);
34 +
	const fetchFeed = async () => {
35 +
		setLoading(true);
36 +
		setError(null);
37 +
		try {
38 +
			const response = await fetch(`${API_URL}/feed`);
39 +
			if (!response.ok) {
40 +
				throw new Error("Failed to fetch feed");
47 41
			}
42 +
			const data: FeedResponse = await response.json();
43 +
			setDocuments(data.documents);
44 +
		} catch (err) {
45 +
			setError(err instanceof Error ? err.message : "Unknown error");
46 +
		} finally {
47 +
			setLoading(false);
48 48
		}
49 +
	};
49 50
51 +
	useEffect(() => {
50 52
		fetchFeed();
51 53
	}, []);
52 54
65 67
		return text.slice(0, maxLength) + "...";
66 68
	};
67 69
68 -
	if (loading) {
69 -
		return (
70 -
			<div className="container">
71 -
				<h1>Standard.site Documents</h1>
72 -
				<p className="loading">Loading documents...</p>
70 +
71 +
	return (
72 +
		<div className="window" style={{ width: "100%", maxWidth: "800px" }}>
73 +
			<div className="title-bar">
74 +
				<div className="title-bar-text">docs.surf - Internet Explorer 6</div>
75 +
				<div className="title-bar-controls">
76 +
					<button aria-label="Minimize" />
77 +
					<button aria-label="Maximize" />
78 +
					<button aria-label="Close" />
79 +
				</div>
73 80
			</div>
74 -
		);
75 -
	}
81 +
			<div className="window-body">
82 +
				<div
83 +
					style={{
84 +
						display: "flex",
85 +
						gap: "8px",
86 +
						marginBottom: "16px",
87 +
						alignItems: "center",
88 +
					}}
89 +
				>
90 +
					<button onClick={fetchFeed}>Refresh</button>
91 +
					<button
92 +
						onClick={() => window.open("https://standard.site", "_blank")}
93 +
					>
94 +
						Standard.site
95 +
					</button>
96 +
					<div style={{ flex: 1 }} />
97 +
					<span>{documents.length} documents</span>
98 +
				</div>
76 99
77 -
	if (error) {
78 -
		return (
79 -
			<div className="container">
80 -
				<h1>Standard.site Documents</h1>
81 -
				<p className="error">Error: {error}</p>
82 -
			</div>
83 -
		);
84 -
	}
100 +
				{loading && <p style={{ textAlign: "center" }}>Searching...</p>}
85 101
86 -
	return (
87 -
		<div className="container">
88 -
			<h1>Standard.site Documents</h1>
89 -
			<p className="subtitle">{documents.length} documents found</p>
102 +
				{error && (
103 +
					<div
104 +
						style={{
105 +
							padding: "10px",
106 +
							background: "#ffefef",
107 +
							border: "1px solid #ff0000",
108 +
						}}
109 +
					>
110 +
						<p>Error: {error}</p>
111 +
					</div>
112 +
				)}
90 113
91 -
			<div className="feed">
92 -
				{documents.map((doc) => (
93 -
					<article key={doc.uri} className="document-card">
94 -
						<h2>
95 -
							{doc.viewUrl ? (
96 -
								<a href={doc.viewUrl} target="_blank" rel="noopener noreferrer">
97 -
									{doc.title}
98 -
								</a>
99 -
							) : (
100 -
								doc.title
101 -
							)}
102 -
						</h2>
103 -
						<time dateTime={doc.publishedAt || undefined}>
104 -
							{formatDate(doc.publishedAt)}
105 -
						</time>
106 -
						{doc.textContent && (
107 -
							<p className="excerpt">{truncateText(doc.textContent)}</p>
108 -
						)}
109 -
						{doc.viewUrl && (
110 -
							<a
111 -
								href={doc.viewUrl}
112 -
								target="_blank"
113 -
								rel="noopener noreferrer"
114 -
								className="read-more"
115 -
							>
116 -
								Read on author's site
117 -
							</a>
118 -
						)}
119 -
					</article>
120 -
				))}
114 +
				{!loading && !error && (
115 +
					<div className="feed" style={{ maxHeight: "70vh", overflowY: "auto", paddingRight: "5px" }}>
116 +
						{documents.map((doc) => (
117 +
							<fieldset key={doc.uri} style={{ marginBottom: "16px" }}>
118 +
								<legend style={{ fontWeight: "bold" }}>
119 +
									{doc.viewUrl ? (
120 +
										<a
121 +
											href={doc.viewUrl}
122 +
											target="_blank"
123 +
											rel="noopener noreferrer"
124 +
											style={{ color: "inherit", textDecoration: "none" }}
125 +
										>
126 +
											{doc.title}
127 +
										</a>
128 +
									) : (
129 +
										doc.title
130 +
									)}
131 +
								</legend>
132 +
								<div style={{ padding: "8px" }}>
133 +
									<div
134 +
										style={{
135 +
											marginBottom: "8px",
136 +
											fontSize: "0.85em",
137 +
											color: "#666",
138 +
										}}
139 +
									>
140 +
										Published: {formatDate(doc.publishedAt)}
141 +
									</div>
142 +
									{doc.textContent && (
143 +
										<p style={{ marginBottom: "12px" }}>
144 +
											{truncateText(doc.textContent)}
145 +
										</p>
146 +
									)}
147 +
									{doc.viewUrl && (
148 +
										<div style={{ textAlign: "right" }}>
149 +
											<button
150 +
												onClick={() =>
151 +
													window.open(doc.viewUrl || "", "_blank")
152 +
												}
153 +
											>
154 +
												Read More
155 +
											</button>
156 +
										</div>
157 +
									)}
158 +
								</div>
159 +
							</fieldset>
160 +
						))}
161 +
						{documents.length === 0 && <p>No documents found.</p>}
162 +
					</div>
163 +
				)}
121 164
			</div>
122 -
123 -
			{documents.length === 0 && <p className="empty">No documents found.</p>}
165 +
			<div className="status-bar">
166 +
				<p className="status-bar-field">Done</p>
167 +
				<p className="status-bar-field">Internet</p>
168 +
			</div>
124 169
		</div>
125 170
	);
126 171
}
packages/client/src/index.css +11 −87
1 -
* {
2 -
  box-sizing: border-box;
3 -
  margin: 0;
4 -
  padding: 0;
5 -
}
6 -
7 1
body {
8 -
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
9 -
    Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
10 -
  line-height: 1.6;
11 -
  color: #333;
12 -
  background: #f5f5f5;
13 -
}
14 -
15 -
.container {
16 -
  max-width: 800px;
17 -
  margin: 0 auto;
18 -
  padding: 2rem 1rem;
19 -
}
20 -
21 -
h1 {
22 -
  font-size: 2rem;
23 -
  margin-bottom: 0.5rem;
24 -
}
25 -
26 -
.subtitle {
27 -
  color: #666;
28 -
  margin-bottom: 2rem;
29 -
}
30 -
31 -
.loading,
32 -
.error,
33 -
.empty {
34 -
  text-align: center;
35 -
  padding: 2rem;
36 -
  color: #666;
37 -
}
38 -
39 -
.error {
40 -
  color: #c00;
41 -
}
42 -
43 -
.feed {
2 +
  background: url("./xp.jpg") no-repeat center center fixed;
3 +
  background-size: cover;
4 +
  min-height: 100vh;
44 5
  display: flex;
45 -
  flex-direction: column;
46 -
  gap: 1.5rem;
47 -
}
48 -
49 -
.document-card {
50 -
  background: white;
51 -
  border-radius: 8px;
52 -
  padding: 1.5rem;
53 -
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
6 +
  align-items: center;
7 +
  justify-content: center;
8 +
  margin: 0;
9 +
  padding: 20px;
54 10
}
55 11
56 -
.document-card h2 {
57 -
  font-size: 1.25rem;
58 -
  margin-bottom: 0.5rem;
59 -
}
60 -
61 -
.document-card h2 a {
62 -
  color: #1a1a1a;
63 -
  text-decoration: none;
64 -
}
65 -
66 -
.document-card h2 a:hover {
67 -
  color: #0066cc;
68 -
  text-decoration: underline;
69 -
}
70 -
71 -
.document-card time {
72 -
  display: block;
73 -
  font-size: 0.875rem;
74 -
  color: #666;
75 -
  margin-bottom: 0.75rem;
76 -
}
77 -
78 -
.document-card .excerpt {
79 -
  color: #444;
80 -
  margin-bottom: 1rem;
81 -
}
82 -
83 -
.document-card .read-more {
84 -
  display: inline-block;
85 -
  color: #0066cc;
86 -
  text-decoration: none;
87 -
  font-size: 0.875rem;
88 -
}
89 -
90 -
.document-card .read-more:hover {
91 -
  text-decoration: underline;
12 +
#root {
13 +
  width: 100%;
14 +
  display: flex;
15 +
  justify-content: center;
92 16
}
packages/client/src/main.tsx +1 −0
2 2
import ReactDOM from "react-dom/client";
3 3
import App from "./App";
4 4
import "./index.css";
5 +
import "xp.css/dist/XP.css";
5 6
6 7
ReactDOM.createRoot(document.getElementById("root")!).render(
7 8
	<React.StrictMode>
packages/client/src/xp.jpg (added) +0 −0

Binary file — no preview.