Home.tsx (11825B)
1 import { useEffect } from "react"; 2 3 import htmlIcon from "../assets/file-type-html.svg"; 4 import jsIcon from "../assets/file-type-js.svg"; 5 import pythonIcon from "../assets/brand-python.svg"; 6 import terminalIcon from "../assets/terminal-2.svg"; 7 import alertTriangle from "../assets/alert-triangle.svg"; 8 import infoCircle from "../assets/info-circle.svg"; 9 10 export default function Home() { 11 useEffect(() => { 12 document.title = "Portfolio - Tom Honour"; 13 }, []); 14 return ( 15 <> 16 <a href="https://tomhonour.neocities.org"> 17 <button className="rounded bg-blue-500 px-4 py-2 font-bold text-white hover:bg-blue-700"> 18 Return to home 19 </button> 20 </a> 21 <div className="m-auto grid max-w-7xl gap-4 font-sans sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3"> 22 {projects.map((project) => ( 23 <div className="mx-2 w-96 max-w-[calc(100vw-calc(var(--spacing)*4))] place-self-center rounded-2xl shadow-xl"> 24 <div className="max-h-48 overflow-hidden"> 25 <img src={project.image} alt={project.alt} className="w-full" /> 26 </div> 27 <div className="p-4"> 28 {!project.metadata.maintained && ( 29 <div className="flex items-center bg-amber-100 p-4 text-orange-800"> 30 <img src={alertTriangle} className="me-2" /> 31 This project {project.metadata.old && "is old and"} isn't 32 being maintained. 33 </div> 34 )} 35 {project.metadata.comingSoon && ( 36 <div className="flex items-center bg-sky-100 p-4 text-blue-800"> 37 <img src={infoCircle} className="me-2" /> 38 Coming soon... 39 </div> 40 )} 41 <div className="my-2"> 42 <div className="me-2 inline"> 43 {project.metadata.icons.includes("terminal") && ( 44 <img src={terminalIcon} /> 45 )} 46 {project.metadata.icons.includes("javascript") && ( 47 <img src={jsIcon} /> 48 )} 49 {project.metadata.icons.includes("html") && ( 50 <img src={htmlIcon} /> 51 )} 52 {project.metadata.icons.includes("python") && ( 53 <img src={pythonIcon} /> 54 )} 55 </div> 56 <h2 className="m-0 inline">{project.title}</h2> 57 </div> 58 <p className="text-gray-500">{project.body}</p> 59 <div className="flex justify-around text-center no-underline"> 60 {project.btn1 && ( 61 <a className="no-underline" href={project.btn1.link}> 62 {project.btn1.name} 63 </a> 64 )} 65 {project.btn2 && ( 66 <a className="no-underline" href={project.btn2.link}> 67 {project.btn2.name} 68 </a> 69 )} 70 {project.btn3 && ( 71 <a className="no-underline" href={project.btn3.link}> 72 {project.btn3.name} 73 </a> 74 )} 75 </div> 76 </div> 77 </div> 78 ))} 79 </div> 80 </> 81 ); 82 } 83 84 const projects = [ 85 { 86 alt: "letterboxd logo", 87 image: "/letterboxd.png", 88 title: "Letterboxd Won't Watch Button (browser extension)", 89 body: "Fade the posters of films you won't watch. Uses the browser extension storage API.", 90 btn1: { 91 name: "View code", 92 link: "https://tomhonour.neocities.org/git/boxd-add-on/files.html", 93 }, 94 btn2: { 95 name: "Get extension", 96 link: "https://addons.mozilla.org/en-GB/firefox/addon/letterboxd-wont-watch-button/", 97 }, 98 metadata: { 99 maintained: false, 100 old: true, 101 icons: ["javascript"], 102 }, 103 }, 104 { 105 alt: "Vala logo", 106 image: "/vala.png", 107 title: "Unnamed Vala Project", 108 body: "Vala is an object-oriented programming language with a self-hosting compiler that generates C code and uses the GObject type system.", 109 metadata: { 110 maintained: true, 111 old: false, 112 comingSoon: true, 113 icons: [""], 114 }, 115 btn1: { 116 name: "Read my blog post", 117 link: "/blog/setting-up-a-vala-development-environment", 118 }, 119 btn2: { 120 name: "Go to site", 121 link: "https://vala.dev", 122 }, 123 }, 124 { 125 alt: "Deno logo", 126 image: "/deno.png", 127 title: "Deno Sandbox command line tool", 128 body: 'Programmatically create git patches for changes on a remote file system (a deno sandbox). Let\'s you modify a git repo safely - for example, when using a "coding agent" (aka RCE agent) - and bring those changes to your machine in the form of a patch file.', 129 metadata: { 130 maintained: false, 131 old: true, 132 comingSoon: false, 133 icons: ["javascript"], 134 }, 135 btn1: { 136 name: "Read my blog post (coming soon)", 137 link: "", 138 }, 139 btn2: { 140 name: "Read code", 141 link: "https://tomhonour.neocities.org/git/denosandbox/file/sandbox.js", 142 }, 143 }, 144 { 145 alt: "yt-dlp logo", 146 image: "/yt-dlp.png", 147 title: "A new video extractor for yt-dlp (in code review)", 148 body: "yt-dlp can download videos from thousands of sites (not just YouTube)! I found a random site which yt-dlp didn't support, and created a video extractor for it.", 149 metadata: { 150 maintained: false, 151 old: true, 152 comingSoon: false, 153 icons: ["python"], 154 }, 155 btn1: { 156 name: "Read code", 157 link: "https://github.com/yt-dlp/yt-dlp/pull/15045/files", 158 }, 159 }, 160 { 161 alt: "OpenFrontIO logo", 162 image: "/openfront.png", 163 title: "OpenFrontIO Teams Game Notifier", 164 body: "Get a notification whenever a public OpenFront teams game (2 or 3 teams) is available/about to start.", 165 metadata: { 166 maintained: false, 167 old: true, 168 comingSoon: false, 169 icons: ["terminal"], 170 }, 171 btn1: { 172 name: "Read code", 173 link: "https://tomhonour.neocities.org/git/old-scripts/file/openfront", 174 }, 175 }, 176 { 177 alt: "ytfzf logo", 178 image: "/ytfzf.png", 179 title: "y (wrapper for ytfzf and yt-dlp)", 180 body: "Download audio or video, and browse channels and search using command line options. Uses ytfzf, yt-dlp, and mpv.", 181 btn1: { 182 name: "Read bash version", 183 link: "https://tomhonour.neocities.org/git/old-scripts/file/y", 184 }, 185 btn2: { 186 name: "Read JavaScript version", 187 link: "https://tomhonour.neocities.org/git/old-scripts/file/node/y.mjs", 188 }, 189 metadata: { 190 maintained: false, 191 old: true, 192 icons: ["terminal", "javascript"], 193 }, 194 }, 195 { 196 alt: "mpv and filmon logos", 197 image: "/filmon.png", 198 title: "TV using mpv, IPC, socat, and filmon", 199 body: "After about 60 seconds of free TV, filmon returns a 403 status code. This script circumvents this restriction by fetching a new stream and directing it to the same mpv window.", 200 btn1: { 201 name: "Read bash version", 202 link: "https://tomhonour.neocities.org/git/old-scripts/file/filmon.sh", 203 }, 204 btn2: { 205 name: "Read JavaScript version", 206 link: "https://tomhonour.neocities.org/git/old-scripts/file/node/filmon.mjs", 207 }, 208 // btn3: { 209 // name: "View video", 210 // link: "/Send_multiple_videos_to_one_mpv_window_using_IPC_and_a_socket__re-upload_.mp4", 211 // }, 212 metadata: { 213 maintained: false, 214 old: true, 215 icons: ["terminal", "javascript"], 216 }, 217 }, 218 // { 219 // alt: "Screenshot of fares tracker", 220 // image: "/fares_tracker.png", 221 // title: "Advance Train Tickets Fare Tracking", 222 // body: "See changes in train ticket fares between now and the end of the booking window.", 223 // metadata: { 224 // maintained: false, 225 //old: true, 226 // icons: ["html", "javascript"] 227 // }, 228 // btn1: { 229 // name: "Visit", 230 // link: "/portfolio/fares" 231 // }, 232 // btn2: { 233 // name: "Read frontend code", 234 // link: "https://git.sr.ht/~tomhonour/filmsbytom/tree/master/item/portfolio/Fares.tsx" 235 // }, 236 // btn3: { 237 // name: "Read backend code", 238 // link: "https://git.sr.ht/~tomhonour/filmsbytom/tree/master/item/backend/routes/fares.ts" 239 // } 240 // }, 241 { 242 alt: "Archi and JavaScipt logos.", 243 image: "/jarchi-utils.png", 244 title: "jarchi-utils", 245 body: "Pure JavaScript implementations of jArchi methods.", 246 metadata: { 247 maintained: false, 248 old: true, 249 icons: ["javascript"], 250 }, 251 btn1: { 252 name: "Go to package", 253 link: "https://www.npmjs.com/package/jarchi-utils", 254 }, 255 btn2: { 256 name: "View project", 257 link: "https://tomhonour.neocities.org/git/jarchi-utils/files.html", 258 }, 259 }, 260 // { 261 // alt: "Wood Job! movie poster in windowboxd", 262 // image: "/woodjob.png", 263 // title: "windowboxd", 264 // body: "Alternative frontend for letterboxd. Requires no JavaScript. Inspired by invidious and nitter.", 265 // metadata: { 266 // maintained: true, 267 //old: true, 268 // icons: ["html", "javascript"] 269 // }, 270 // btn1: { 271 // name: "Go to repository", 272 // link: "https://github.com/5-tom/windowboxd" 273 // } 274 // }, 275 // { 276 // alt: "pipes", 277 // image: "/littlepipelines.png", 278 // title: "LittlePipelines (Contributor to)", 279 // body: "Learn to code through pull request reviews.", 280 // metadata: { 281 // maintained: true, 282 //old: true, 283 // comingSoon: true, 284 // icons: ["html", "javascript"] 285 // } 286 // }, 287 { 288 alt: "kodi file structure", 289 image: "/kodi.png", 290 title: "Organise movies to the Kodi recommendation", 291 body: "Kodi recommends that movie libraries be organised like: 'Dredd (2012)' > 'Dredd (2012).mkv'. This script batch renames and batch moves your movies to conform to Kodi's recommendation.", 292 btn1: { 293 name: "Read bash version", 294 link: "https://tomhonour.neocities.org/git/old-scripts/file/kodi", 295 }, 296 btn2: { 297 name: "Read JavaScript version", 298 link: "https://tomhonour.neocities.org/git/old-scripts/file/node/kodi.mjs", 299 }, 300 btn3: { 301 name: "Read recommendation", 302 link: "https://kodi.wiki/view/Naming_video_files/Movies", 303 }, 304 metadata: { 305 maintained: false, 306 old: true, 307 icons: ["terminal", "javascript"], 308 }, 309 }, 310 { 311 alt: "GitHub Copilot for VSCodium logos", 312 image: "/copilot.png", 313 title: "Install GitHub Copilot for VSCodium (snap)", 314 body: "The Copilot extension can't be installed for VSCodium without a little bit of hacking!", 315 btn1: { 316 name: "Read code", 317 link: "https://tomhonour.neocities.org/git/old-scripts/file/copilot.sh", 318 }, 319 metadata: { 320 maintained: false, 321 old: true, 322 icons: ["terminal"], 323 }, 324 }, 325 { 326 alt: "GJS logo", 327 image: "/jsgnome.png", 328 title: "Unnamed GJS Project", 329 body: "GJS is a language binding for JavaScript and GTK. It lets you write native Linux desktop apps with JavaScript.", 330 metadata: { 331 maintained: true, 332 old: false, 333 comingSoon: true, 334 icons: ["javascript"], 335 }, 336 btn1: { 337 name: "Watch talk", 338 link: "https://www.youtube.com/watch?v=YqmmK16rIm4", 339 }, 340 btn2: { 341 name: "Read README", 342 link: "https://gitlab.gnome.org/GNOME/gjs/-/blob/master/doc/README.md", 343 }, 344 btn3: { 345 name: "GTK4 + GJS Book", 346 link: "https://rmnvgr.gitlab.io/gtk4-gjs-book/", 347 }, 348 }, 349 // { 350 // alt: "pipes", 351 // image: "/dataducts.jpg", 352 // title: "DataDucts", 353 // body: "Webmaster for a digital transformation company.", 354 // metadata: { 355 // maintained: true, 356 //old: true, 357 // comingSoon: true, 358 // icons: ["html", "javascript"] 359 // } 360 // }, 361 // { 362 // alt: "Model of 3D man with magnifying glass", 363 // image: "/3d_man.jpeg", 364 // title: "Tab over words in text and copy to clipboard (terminal)", 365 // body: "Like less and man's jump to next word, this tool lets you cycle over the words in a text file. After pressing Enter, the highlighted word is copied to your clipboard.", 366 // btn1: { 367 // name: "View code", 368 // link: "https://git.sr.ht/~tomhonour/old-scripts/tree/master/item/node/copy-word.mjs", 369 // }, 370 // // btn2: { 371 // // name: "View video", 372 // // link: "/Terminal_tab_over_words_and_copy_to_clipboard.mp4", 373 // // }, 374 // metadata: { 375 // maintained: false, 376 // old: true, 377 // icons: ["terminal", "javascript"], 378 // }, 379 // }, 380 { 381 alt: "Photo of Tom Honour", 382 image: "/photo.jpeg", 383 title: "This site", 384 body: "A static homepage, a react portfolio, and a Hugo blog.", 385 btn1: { 386 name: "View code", 387 link: "https://tomhonour.neocities.org/git/website/files.html", 388 }, 389 // btn2: { 390 // name: "View video", 391 // link: "/Terminal_tab_over_words_and_copy_to_clipboard.mp4", 392 // }, 393 metadata: { 394 maintained: true, 395 old: false, 396 icons: ["html", "javascript"], 397 }, 398 }, 399 ];