diff --git a/README.md b/README.md index d7a201f..f043871 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This guide is intended to help you get started with the coworking streams! Wheth I am a coworking streamer myself and I also made several open source widgets for the coworking streams. -Please feel free to drop by my stream! +Please feel free to drop by my stream! --- diff --git a/astro.config.mjs b/astro.config.mjs index ba4498c..f2a5182 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,12 +1,13 @@ import { defineConfig } from "astro/config"; import tailwind from "@astrojs/tailwind"; - import mdx from "@astrojs/mdx"; +import react from "@astrojs/react"; + // https://astro.build/config export default defineConfig({ - integrations: [tailwind(), mdx()], - server: { - port: 3000 - } -}); \ No newline at end of file + integrations: [tailwind(), mdx(), react()], + server: { + port: 3000, + }, +}); diff --git a/package-lock.json b/package-lock.json index 6f027ec..66d0a83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,23 @@ "version": "2.0.0", "dependencies": { "@astrojs/mdx": "^2.1.1", + "@astrojs/react": "^3.1.0", "@astrojs/tailwind": "^5.1.0", "@astrojs/ts-plugin": "^1.5.3", + "@types/react": "^18.2.65", + "@types/react-dom": "^18.2.22", "@vercel/analytics": "^1.0.1", "astro": "^4.4.1", + "clsx": "^2.1.0", + "framer-motion": "^11.0.12", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.22.3", + "tailwind-merge": "^2.2.1", "tailwindcss": "^3.4.1" }, "devDependencies": { + "@types/react-router-dom": "^5.3.3", "prettier": "^3.2.5", "prettier-plugin-tailwindcss": "^0.5.11" } @@ -113,6 +123,24 @@ "node": ">=18.14.1" } }, + "node_modules/@astrojs/react": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@astrojs/react/-/react-3.1.0.tgz", + "integrity": "sha512-KZhZyV+sUDZEjlrmPWNiPGeYowG9uq6/aMbCgVaeKEBlWT4Kg32TNwBOhZk6AcE4LY1l3mIwt3orUGE2JV96/g==", + "dependencies": { + "@vitejs/plugin-react": "^4.2.0", + "ultrahtml": "^1.3.0" + }, + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "@types/react": "^17.0.50 || ^18.0.21", + "@types/react-dom": "^17.0.17 || ^18.0.6", + "react": "^17.0.2 || ^18.0.0", + "react-dom": "^17.0.2 || ^18.0.0" + } + }, "node_modules/@astrojs/tailwind": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@astrojs/tailwind/-/tailwind-5.1.0.tgz", @@ -459,6 +487,45 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", + "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz", + "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", + "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.23.9", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", @@ -1015,6 +1082,14 @@ "node": ">=14" } }, + "node_modules/@remix-run/router": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", + "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", @@ -1245,6 +1320,12 @@ "@types/unist": "*" } }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "dev": true + }, "node_modules/@types/mdast": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz", @@ -1286,6 +1367,55 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/prop-types": { + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" + }, + "node_modules/@types/react": { + "version": "18.2.65", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.65.tgz", + "integrity": "sha512-98TsY0aW4jqx/3RqsUXwMDZSWR1Z4CUlJNue8ueS2/wcxZOsz4xmW1X8ieaWVRHcmmQM3R8xVA4XWB3dJnWwDQ==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.22", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.22.tgz", + "integrity": "sha512-fHkBXPeNtfvri6gdsMYyW+dW7RXFo6Ad09nLFK0VQWR7yGLai/Cyvyj696gbwYvBnhGtevUG9cET0pmUbMtoPQ==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "dev": true, + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dev": true, + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" + }, "node_modules/@types/unist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", @@ -1301,6 +1431,24 @@ "resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-1.0.1.tgz", "integrity": "sha512-Ux0c9qUfkcPqng3vrR0GTrlQdqNJ2JREn/2ydrVuKwM3RtMfF2mWX31Ijqo1opSjNAq6rK76PwtANw6kl6TAow==" }, + "node_modules/@vitejs/plugin-react": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", + "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==", + "dependencies": { + "@babel/core": "^7.23.5", + "@babel/plugin-transform-react-jsx-self": "^7.23.3", + "@babel/plugin-transform-react-jsx-source": "^7.23.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0" + } + }, "node_modules/@volar/language-core": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.0.4.tgz", @@ -2118,6 +2266,11 @@ "node": ">=4" } }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2568,6 +2721,30 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "11.0.12", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.0.12.tgz", + "integrity": "sha512-1VW4pk+4EH8RwWHdqntWTwF9peranyHn/BczvMzF9TGh/FwMKxoRZzkig8Nak9BQA8GymfIyCGo5adXwRuXDXA==", + "dependencies": { + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -3425,6 +3602,17 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -5451,6 +5639,67 @@ "rc": "cli.js" } }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-refresh": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", + "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", + "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", + "dependencies": { + "@remix-run/router": "1.15.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", + "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", + "dependencies": { + "@remix-run/router": "1.15.3", + "react-router": "6.22.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -5491,6 +5740,11 @@ "node": ">=8.10.0" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/rehype": { "version": "13.0.1", "resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.1.tgz", @@ -6125,6 +6379,14 @@ } ] }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/section-matter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", @@ -6545,6 +6807,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tailwind-merge": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.2.1.tgz", + "integrity": "sha512-o+2GTLkthfa5YUt4JxPfzMIpQzZ3adD1vLVkvKE1Twl9UAhGsEbIZhHHZVRttyW177S8PDJI3bTQNaebyofK3Q==", + "dependencies": { + "@babel/runtime": "^7.23.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", @@ -6705,6 +6979,11 @@ } } }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -6728,6 +7007,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ultrahtml": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.5.3.tgz", + "integrity": "sha512-GykOvZwgDWZlTQMtp5jrD4BVL+gNn2NVlVafjcFUJ7taY20tqYdwdoWBFy6GBJsNTZe1GkGPkSl5knQAjtgceg==" + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", diff --git a/package.json b/package.json index e6d83ad..c23021a 100644 --- a/package.json +++ b/package.json @@ -11,13 +11,23 @@ }, "dependencies": { "@astrojs/mdx": "^2.1.1", + "@astrojs/react": "^3.1.0", "@astrojs/tailwind": "^5.1.0", "@astrojs/ts-plugin": "^1.5.3", + "@types/react": "^18.2.65", + "@types/react-dom": "^18.2.22", "@vercel/analytics": "^1.0.1", "astro": "^4.4.1", + "clsx": "^2.1.0", + "framer-motion": "^11.0.12", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.22.3", + "tailwind-merge": "^2.2.1", "tailwindcss": "^3.4.1" }, "devDependencies": { + "@types/react-router-dom": "^5.3.3", "prettier": "^3.2.5", "prettier-plugin-tailwindcss": "^0.5.11" } diff --git a/src/components/BlogPostPreview.astro b/src/components/BlogPostPreview.astro deleted file mode 100644 index e275758..0000000 --- a/src/components/BlogPostPreview.astro +++ /dev/null @@ -1,47 +0,0 @@ ---- -function timeAgo(date: Date) { - const dateObject = new Date(date); - const seconds = Math.floor( - (new Date().getTime() - dateObject.getTime()) / 1000, - ); - let interval = Math.floor(seconds / 31536000); - - if (interval >= 1) { - return interval + (interval === 1 ? " year ago" : " years ago"); - } - interval = Math.floor(seconds / 2592000); - if (interval >= 1) { - return interval + (interval === 1 ? " month ago" : " months ago"); - } - interval = Math.floor(seconds / 86400); - if (interval >= 1) { - return interval + (interval === 1 ? " day ago" : " days ago"); - } - interval = Math.floor(seconds / 3600); - if (interval >= 1) { - return interval + (interval === 1 ? " hour ago" : " hours ago"); - } - interval = Math.floor(seconds / 60); - if (interval > 1) { - return interval + (interval === 1 ? " minute ago" : " minutes ago"); - } - return ( - Math.floor(seconds) + (seconds === 1 ? " second ago" : " seconds ago") - ); -} - -const { title, tagline, pubDate, url } = Astro.props; ---- - - -
-

{title}

-

{tagline}

-

{timeAgo(new Date(pubDate))}

-
-
diff --git a/src/components/blocks/BlogPostPreview.astro b/src/components/blocks/BlogPostPreview.astro new file mode 100644 index 0000000..e4decb0 --- /dev/null +++ b/src/components/blocks/BlogPostPreview.astro @@ -0,0 +1,47 @@ +--- +function timeAgo(date: Date) { + const dateObject = new Date(date); + const seconds = Math.floor( + (new Date().getTime() - dateObject.getTime()) / 1000 + ); + let interval = Math.floor(seconds / 31536000); + + if (interval >= 1) { + return interval + (interval === 1 ? " year ago" : " years ago"); + } + interval = Math.floor(seconds / 2592000); + if (interval >= 1) { + return interval + (interval === 1 ? " month ago" : " months ago"); + } + interval = Math.floor(seconds / 86400); + if (interval >= 1) { + return interval + (interval === 1 ? " day ago" : " days ago"); + } + interval = Math.floor(seconds / 3600); + if (interval >= 1) { + return interval + (interval === 1 ? " hour ago" : " hours ago"); + } + interval = Math.floor(seconds / 60); + if (interval > 1) { + return interval + (interval === 1 ? " minute ago" : " minutes ago"); + } + return ( + Math.floor(seconds) + (seconds === 1 ? " second ago" : " seconds ago") + ); +} + +const { title, tagline, pubDate, url } = Astro.props; +--- + + +
+

{title}

+

{tagline}

+

{timeAgo(new Date(pubDate))}

+
+
diff --git a/src/components/blocks/Hero.astro b/src/components/blocks/Hero.astro new file mode 100644 index 0000000..cd3f344 --- /dev/null +++ b/src/components/blocks/Hero.astro @@ -0,0 +1,52 @@ +--- +import { HoverEffect } from "../ui/card-hover-effect"; +import HeroTitle from "../ui/HeroTitle.astro"; + +const items = [ + { + title: "Introduction", + description: + "What is Coworking? What are study streams? What's the purpose of it?", + link: "/get-started/introduction", + }, + { + title: "Tips", + description: + "Learn some tips to enhance your stream and attract more viewers!", + link: "/guide/general-tips", + }, + { + title: "Music and Audio", + description: + "Check out non-copyrighted playlists, non-music background audio and many more!", + link: "/guide/music-and-audio", + }, + { + title: "Groups", + description: + "Check out some awesome communities related to coworking, studying and self-improvement!", + link: "/groups", + }, + { + title: "Blog", + description: + "Take a look at some blog posts regarding the coworking community!", + link: "/blog", + }, + { + title: "Credits", + description: "Support the people behind this website! Drop them", + link: "/credits", + }, +]; +--- + +
+ + +
+ +
+
+ + diff --git a/src/components/TableOfContent.astro b/src/components/blocks/TableOfContent.astro similarity index 100% rename from src/components/TableOfContent.astro rename to src/components/blocks/TableOfContent.astro diff --git a/src/components/blocks/navBar.astro b/src/components/blocks/navBar.astro new file mode 100644 index 0000000..069e11e --- /dev/null +++ b/src/components/blocks/navBar.astro @@ -0,0 +1,154 @@ +--- +import VerticalNav from "./verticalNav.astro"; +--- + +
+
+ +
+ +
+ + + + + +
+ +
+
+
+ + + diff --git a/src/components/blocks/verticalNav.astro b/src/components/blocks/verticalNav.astro new file mode 100644 index 0000000..cf5b249 --- /dev/null +++ b/src/components/blocks/verticalNav.astro @@ -0,0 +1,154 @@ +--- +type Navigation = Record< + string, + { title: string; url: string; order: number }[] +>; + +const currentUrl = Astro.request.url; + +const isCurrentUrl = (url: string) => currentUrl.includes(url); + +export function formatTitle(title: string): string { + return title + .split("-") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); +} + +export async function setup() { + // setup navigation + const getStartedFiles = await Astro.glob("../../pages/get-started/*.md"); + const guideFiles = await Astro.glob("../../pages/guide/*.md"); + const markdownFiles = [...getStartedFiles, ...guideFiles]; + const navigation: Navigation = {}; + + for (const file of markdownFiles) { + const filePath = file.url; + const [_, category] = filePath!.split("/"); + const formattedCategory = formatTitle(category); + const formattedTitle = file.frontmatter.title; + const order = file.frontmatter.order ?? 0; + + if (!navigation[formattedCategory]) { + navigation[formattedCategory] = []; + } + + navigation[formattedCategory].push({ + title: formattedTitle, + url: filePath || "", + order, + }); + } + + // sort by "order" + const sortedNavigation: Navigation = {}; + Object.keys(navigation) + .sort() + .forEach((key) => { + sortedNavigation[key] = navigation[key].sort( + (a, b) => a.order - b.order + ); + }); + + return { + navigation: sortedNavigation, + }; +} + +interface Props { + showHome?: boolean | null; +} + +const { navigation } = await setup(); +const { showHome } = Astro.props; +--- + +
+ +
+ + diff --git a/src/components/navBar.astro b/src/components/navBar.astro deleted file mode 100644 index d02444e..0000000 --- a/src/components/navBar.astro +++ /dev/null @@ -1,230 +0,0 @@ - -
-
- -
- -
- - - - - -
- -
-
-
- - diff --git a/src/components/ui/BlurBackground.astro b/src/components/ui/BlurBackground.astro new file mode 100644 index 0000000..abf3e15 --- /dev/null +++ b/src/components/ui/BlurBackground.astro @@ -0,0 +1,26 @@ +
+
+
+
+ + diff --git a/src/components/ui/HeroTitle.astro b/src/components/ui/HeroTitle.astro new file mode 100644 index 0000000..8a09679 --- /dev/null +++ b/src/components/ui/HeroTitle.astro @@ -0,0 +1,15 @@ +
+
+ Getting started with +
+ Coworking streams +
+
+
+ Brought to you by RythonDev x Coworking Crew +
+
diff --git a/src/components/ui/card-hover-effect.tsx b/src/components/ui/card-hover-effect.tsx new file mode 100644 index 0000000..0628a43 --- /dev/null +++ b/src/components/ui/card-hover-effect.tsx @@ -0,0 +1,115 @@ +import { cn } from "../../../utils/cn"; +import { AnimatePresence, motion } from "framer-motion"; +import { useState } from "react"; + +export const HoverEffect = ({ + items, + className, +}: { + items: { + title: string; + description: string; + link: string; + }[]; + className?: string; +}) => { + let [hoveredIndex, setHoveredIndex] = useState(null); + + return ( +
+ {items.map((item, idx) => ( + setHoveredIndex(idx)} + onMouseLeave={() => setHoveredIndex(null)} + > + + {hoveredIndex === idx && ( + + )} + + + {item.title} + {item.description} + + + ))} +
+ ); +}; + +export const Card = ({ + className, + children, +}: { + className?: string; + children: React.ReactNode; +}) => { + return ( +
+
+
{children}
+
+
+ ); +}; +export const CardTitle = ({ + className, + children, +}: { + className?: string; + children: React.ReactNode; +}) => { + return ( +

+ {children} +

+ ); +}; +export const CardDescription = ({ + className, + children, +}: { + className?: string; + children: React.ReactNode; +}) => { + return ( +

+ {children} +

+ ); +}; diff --git a/src/components/verticalNav.astro b/src/components/verticalNav.astro deleted file mode 100644 index c7084a4..0000000 --- a/src/components/verticalNav.astro +++ /dev/null @@ -1,72 +0,0 @@ ---- -type Navigation = Record< - string, - { title: string; url: string; order: number }[] ->; - -const navigation = Astro.props.navigation as Navigation; -const currentUrl = Astro.request.url; - -const isCurrentUrl = (url: string) => currentUrl.includes(url); ---- - - diff --git a/src/content/posts/1-coworking-category.md b/src/content/posts/1-coworking-category.md index bb65634..dd4ab22 100644 --- a/src/content/posts/1-coworking-category.md +++ b/src/content/posts/1-coworking-category.md @@ -104,6 +104,10 @@ Here are the listed pros and cons of using the "Coworking" category, which may o The purpose of this post is not to persuade people to "pick a side", rather it is meant to inform and show the bigger picture of this "Coworking" category. Please do not pressure nor target any streamers regarding this topic, and don't forget to [VOTE for an official Coworking and Study category](https://twitch.uservoice.com/forums/310210-discover/suggestions/46004791-coworking). +## Edit: + +As of 15th March 2024, the "Coworking" game category has been removed from IGDB and Twitch. + --- by [RythonDev](https://twitch.tv/RythonDev) \ No newline at end of file diff --git a/src/layouts/BlogPostLayout.astro b/src/layouts/BlogPostLayout.astro index aad0d8c..b89e6c8 100644 --- a/src/layouts/BlogPostLayout.astro +++ b/src/layouts/BlogPostLayout.astro @@ -1,156 +1,35 @@ --- import Layout from "./Layout.astro"; -import TableOfContent from "../components/TableOfContent.astro"; -import VerticalNav from "../components/verticalNav.astro"; +import TableOfContent from "../components/blocks/TableOfContent.astro"; +import VerticalNav from "../components/blocks/verticalNav.astro"; -type Navigation = Record< - string, - { title: string; url: string; order: number }[] ->; - -export function formatTitle(title: string): string { - return title - .split("-") - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(" "); -} - -export async function setup() { - // setup navigation - const getStartedFiles = await Astro.glob("../pages/get-started/*.md"); - const guideFiles = await Astro.glob("../pages/guide/*.md"); - const markdownFiles = [...getStartedFiles, ...guideFiles]; - const navigation: Navigation = {}; - - for (const file of markdownFiles) { - const filePath = file.url; - const [_, category] = filePath!.split("/"); - const formattedCategory = formatTitle(category); - const formattedTitle = file.frontmatter.title; - const order = file.frontmatter.order ?? 0; - - if (!navigation[formattedCategory]) { - navigation[formattedCategory] = []; - } - - navigation[formattedCategory].push({ - title: formattedTitle, - url: filePath || "", - order, - }); - } - - // sort by "order" - const sortedNavigation: Navigation = {}; - Object.keys(navigation) - .sort() - .forEach((key) => { - sortedNavigation[key] = navigation[key].sort( - (a, b) => a.order - b.order, - ); - }); - - return { - navigation: sortedNavigation, - }; -} const { frontmatter, headings } = Astro.props; -const { navigation } = await setup(); --- -
-
- -
- -
-
-
-
- -
-
- - -
-
-
-
-
- - - - +
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
diff --git a/src/layouts/BlogsLayout.astro b/src/layouts/BlogsLayout.astro index 0ae54d4..94e7a51 100644 --- a/src/layouts/BlogsLayout.astro +++ b/src/layouts/BlogsLayout.astro @@ -1,70 +1,21 @@ --- import Layout from "./Layout.astro"; -import VerticalNav from "../components/verticalNav.astro"; - -type Navigation = Record< - string, - { title: string; url: string; order: number }[] ->; - -export function formatTitle(title: string): string { - return title - .split("-") - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(" "); -} - -export async function setup() { - // setup navigation - const getStartedFiles = await Astro.glob("../pages/get-started/*.md"); - const guideFiles = await Astro.glob("../pages/guide/*.md"); - const markdownFiles = [...getStartedFiles, ...guideFiles]; - const navigation: Navigation = {}; - - for (const file of markdownFiles) { - const filePath = file.url; - const [_, category] = filePath!.split("/"); - const formattedCategory = formatTitle(category); - const formattedTitle = file.frontmatter.title; - const order = file.frontmatter.order ?? 0; - - if (!navigation[formattedCategory]) { - navigation[formattedCategory] = []; - } - - navigation[formattedCategory].push({ - title: formattedTitle, - url: filePath || "", - order, - }); - } - - // sort by "order" - const sortedNavigation: Navigation = {}; - Object.keys(navigation) - .sort() - .forEach((key) => { - sortedNavigation[key] = navigation[key].sort( - (a, b) => a.order - b.order, - ); - }); - - return { - navigation: sortedNavigation, - }; -} -const { navigation } = await setup(); +import VerticalNav from "../components/blocks/verticalNav.astro"; --- -
-
- -
- -
-
- +
+ +
+
+ - -
-
-
+ +
+ + - +
diff --git a/src/layouts/GuideLayout.astro b/src/layouts/GuideLayout.astro index d1701af..8686bb7 100644 --- a/src/layouts/GuideLayout.astro +++ b/src/layouts/GuideLayout.astro @@ -1,148 +1,100 @@ --- import Layout from "./Layout.astro"; -import TableOfContent from "../components/TableOfContent.astro"; -import VerticalNav from "../components/verticalNav.astro"; +import TableOfContent from "../components/blocks/TableOfContent.astro"; +import VerticalNav from "../components/blocks/verticalNav.astro"; -type Navigation = Record< - string, - { title: string; url: string; order: number }[] ->; - -export function formatTitle(title: string): string { - return title - .split("-") - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(" "); -} - -export async function setup() { - // setup navigation - const getStartedFiles = await Astro.glob("../pages/get-started/*.md"); - const guideFiles = await Astro.glob("../pages/guide/*.md"); - const markdownFiles = [...getStartedFiles, ...guideFiles]; - const navigation: Navigation = {}; - - for (const file of markdownFiles) { - const filePath = file.url; - const [_, category] = filePath!.split("/"); - const formattedCategory = formatTitle(category); - const formattedTitle = file.frontmatter.title; - const order = file.frontmatter.order ?? 0; - - if (!navigation[formattedCategory]) { - navigation[formattedCategory] = []; - } - - navigation[formattedCategory].push({ - title: formattedTitle, - url: filePath || "", - order, - }); - } - - // sort by "order" - const sortedNavigation: Navigation = {}; - Object.keys(navigation) - .sort() - .forEach((key) => { - sortedNavigation[key] = navigation[key].sort( - (a, b) => a.order - b.order, - ); - }); - - return { - navigation: sortedNavigation, - }; -} const { frontmatter, headings } = Astro.props; -const { navigation } = await setup(); --- -
-
- -
- -
-
-
-
- -
- -
-
-
-
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
- + - +
diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index db5974a..6c56f76 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -1,10 +1,10 @@ --- -import NavBar from "../components/navBar.astro"; +import NavBar from "../components/blocks/navBar.astro"; import { ViewTransitions } from "astro:transitions"; export interface Props { - title: string; - description: string; + title: string; + description: string; } const { title, description } = Astro.props; @@ -12,201 +12,144 @@ const { title, description } = Astro.props; - - - - - - - - - - - - {title} - - - - + + + + + + + + + + + + {title} + + +
+ + +
- + - + // make all attributes have target="_blank" except for Table of Contents + document + .querySelectorAll("a:not(.no-target-blank)") + .forEach((el) => { + el.setAttribute("target", "_blank"); + }); + + diff --git a/src/pages/blog/index.astro b/src/pages/blog/index.astro index 7651267..46e5d50 100644 --- a/src/pages/blog/index.astro +++ b/src/pages/blog/index.astro @@ -1,26 +1,26 @@ --- import BlogsLayout from "../../layouts/BlogsLayout.astro"; -import BlogPost from "../../components/BlogPostPreview.astro"; +import BlogPost from "../../components/blocks/BlogPostPreview.astro"; import { getCollection } from "astro:content"; const allPosts = await getCollection("posts"); --- -

Blog Posts

-
- { - allPosts.map((post) => ( - - )) - } -
+

Blog Posts

+
+ { + allPosts.map((post) => ( + + )) + } +
diff --git a/src/pages/credits/index.md b/src/pages/credits/index.md index c948655..4e166fd 100644 --- a/src/pages/credits/index.md +++ b/src/pages/credits/index.md @@ -16,6 +16,18 @@ Credits to the wonderful team of people behind this guide! Main developer of the project -- [Twitch](https://twitch.tv/RythonDev) -- [YouTube](https://youtube.com/@RythonDev) -- [Website](https://rython.dev/) \ No newline at end of file +
+ +[![Twitch Badge](https://img.shields.io/twitch/status/rythondev?color=9146FF&label=Twitch&logo=twitch&logoColor=white&style=for-the-badge)](https://twitch.tv/RythonDev) + +[![YouTube Badge](https://img.shields.io/badge/YouTube-F00?logo=youtube&logoColor=fff&style=for-the-badge)](https://youtube.com/@RythonDev) + +[![Website](https://img.shields.io/badge/Website-fff?logoColor=000&style=for-the-badge)](https://rython.dev/) + +[![Ko-fi Badge](https://img.shields.io/badge/Ko--fi-FF5E5B?logo=kofi&logoColor=fff&style=for-the-badge)](https://ko-fi.com/rython) + +
+ + + + \ No newline at end of file diff --git a/src/pages/guide/bots.md b/src/pages/guide/bots.md index 769ec19..915b629 100644 --- a/src/pages/guide/bots.md +++ b/src/pages/guide/bots.md @@ -17,16 +17,33 @@ Useful bots to add to your stream!
+Available on: + ![Twitch Badge](https://img.shields.io/badge/Twitch-9146FF?logo=twitch&logoColor=fff&style=for-the-badge)
Sery Bot is a moderation bot providing anti-hate raid and anti-follow bot protections, and other safety features and tools to streamers. +### [MohCitrus 🔗](https://www.twitch.tv/MohCitrus/about) + +
+ +Available on: + +![Twitch Badge](https://img.shields.io/badge/Twitch-9146FF?logo=twitch&logoColor=fff&style=for-the-badge) +
+ + +MohCitrus is a translator bot made by [MohFocus (Twitch)](https://twitch.tv/MohFocus)! It can translate messages of viewers to any language via commands! +![MohCitrus](/images/bots/mohcitrus.webp) + ### [Nightbot 🔗](https://nightbot.tv/)
+Available on: + ![Twitch Badge](https://img.shields.io/badge/Twitch-9146FF?logo=twitch&logoColor=fff&style=for-the-badge) ![YouTube Badge](https://img.shields.io/badge/YouTube-F00?logo=youtube&logoColor=fff&style=for-the-badge) @@ -34,16 +51,18 @@ Sery Bot is a moderation bot providing anti-hate raid and anti-follow bot protec Nightbot is a chatbot that you can use to manage your chat. It has a lot of features and is easy to use. You can also use it to create custom commands for your stream. -### [MohCitrus 🔗](https://www.twitch.tv/MohCitrus/about) +### [BotRix 🔗](https://botrix.live/)
+Available on: + ![Twitch Badge](https://img.shields.io/badge/Twitch-9146FF?logo=twitch&logoColor=fff&style=for-the-badge) + +![YouTube Badge](https://img.shields.io/badge/YouTube-F00?logo=youtube&logoColor=fff&style=for-the-badge)
- -MohCitrus is a translator bot made by [MohFocus (Twitch)](https://twitch.tv/MohFocus)! It can translate messages of viewers to any language via commands! -![MohCitrus](/images/bots/mohcitrus.webp) +BotRix is a chat bot that offers features and tools to enhance your community experience. You can use BotRix to moderate, send notifications, play minigames and more on Twitch, YouTube and Discord. --- @@ -53,27 +72,29 @@ MohCitrus is a translator bot made by [MohFocus (Twitch)](https://twitch.tv/MohF
+Available on: + ![Twitch Badge](https://img.shields.io/badge/Twitch-9146FF?logo=twitch&logoColor=fff&style=for-the-badge) ![YouTube Badge](https://img.shields.io/badge/YouTube-F00?logo=youtube&logoColor=fff&style=for-the-badge) + +![Windows Badge](https://img.shields.io/badge/Windows-0078D4?logo=windows&logoColor=fff&style=for-the-badge)
-- Windows only! - -Streamer.bot is a local bot, meaning all connections are made directly from your local PC to any configured external services, such as Twitch, YouTube, etc. - -This means you have full ownership and control of any data stored in Streamer.bot, and your bot does not depend on a central 3rd party service to continue operating. +Supercharge your livestream! Add advanced levels of interactivity to your streams with Streamer.bot. ### [Mix It Up bot 🔗](https://mixitupapp.com/)
+Available on: + ![Twitch Badge](https://img.shields.io/badge/Twitch-9146FF?logo=twitch&logoColor=fff&style=for-the-badge) ![YouTube Badge](https://img.shields.io/badge/YouTube-F00?logo=youtube&logoColor=fff&style=for-the-badge) -
-- Windows only! +![Windows Badge](https://img.shields.io/badge/Windows-0078D4?logo=windows&logoColor=fff&style=for-the-badge) +
Mix It Up is a free, full featured, community driven, open-source stream bot developed by streamers for streamers. diff --git a/src/pages/guide/music-and-audio.md b/src/pages/guide/music-and-audio.md index 795a821..5fafd40 100644 --- a/src/pages/guide/music-and-audio.md +++ b/src/pages/guide/music-and-audio.md @@ -7,7 +7,7 @@ order: 1 # Music and Audio -Every Coworking & Study stream has their vibe based on their stream audio! +The vibe of a coworking stream is determined by the background music or audio! --- @@ -116,12 +116,17 @@ There you can have your different outputs for different programmes. For example, ### Complicated ways -- #### [VoicemeeterBanana](https://vb-audio.com/Voicemeeter/banana.htm) (Windows only!) +- #### [VoicemeeterBanana](https://vb-audio.com/Voicemeeter/banana.htm) + ![Windows Badge](https://img.shields.io/badge/Windows-0078D4?logo=windows&logoColor=fff&style=for-the-badge) - Download Voicemeeter Banana (software to route audio sources) and set all the audio sources/inputs you don't want to include on stream into a Voicemeeter input/output source separate from the main routing. - The outputs will show up separately in your Audio Mixer, which you can adjust/mute - Tutorial: [Separate Game Audio, Discord, Music in OBS! (A Voicemeeter Tutorial for Beginners)](https://www.youtube.com/watch?v=XD9sWOjITYU&t=376s) -- #### [BlackHole audio](https://obsproject.com/forum/resources/mac-desktop-audio-using-blackhole.1191/) (Mac only!) + +- #### [BlackHole audio](https://obsproject.com/forum/resources/mac-desktop-audio-using-blackhole.1191/) + ![macOS Badge](https://img.shields.io/badge/macOS-fff?logo=macos&logoColor=000&style=for-the-badge) - Free! Integrates with OBS -- #### [GroundControl Caster](https://gingeraudio.com/groundcontrol-caster/) (Mac only!) + +- #### [GroundControl Caster](https://gingeraudio.com/groundcontrol-caster/) + ![macOS Badge](https://img.shields.io/badge/macOS-fff?logo=macos&logoColor=000&style=for-the-badge) - Free! Integrates with Twitch, OBS, StreamLabs, Zoom, FaceTime etc. diff --git a/src/pages/index.astro b/src/pages/index.astro index 8757b79..548434d 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,62 +1,17 @@ --- import Layout from "../layouts/Layout.astro"; +import Hero from "../components/blocks/Hero.astro"; +import BlurBackground from "../components/ui/BlurBackground.astro"; --- -
+
+ +
+ +
+
- - diff --git a/tailwind.config.cjs b/tailwind.config.cjs index aa930eb..f2f0bf9 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -4,10 +4,8 @@ module.exports = { theme: { extend: { backgroundImage: (theme) => ({ - "gradient-blob": - "linear-gradient(to right, #FF7782, rgba(0,0,0,0))", "gradient-pink-cyan": - "linear-gradient(to right, #FF7782, #8CC0DE)", + "linear-gradient(to right, #FF7782, aquamarine)", }), keyframes: { rotate: { @@ -15,14 +13,26 @@ module.exports = { "50%": { scale: "1 1.5" }, "100%": { transform: "rotate(360deg)" }, }, + gradient: { + "0%": { + "background-position": "0% 50%", + }, + "50%": { + "background-position": "100% 50%", + }, + "100%": { + "background-position": "0% 50%", + }, + }, }, animation: { - "spin-slow": "rotate 5s linear infinite", + "spin-slow": "rotate 15s linear infinite", + gradient: "gradient 15s ease infinite", }, colors: { "cowo-pink": "#FF7782", - "secondary": "#171717", - "primary": "#fafafa", + secondary: "#171717", + primary: "#fafafa", }, }, }, diff --git a/tsconfig.json b/tsconfig.json index 77da9dd..032ad64 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,3 +1,7 @@ { - "extends": "astro/tsconfigs/strict" + "extends": "astro/tsconfigs/strict", + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "react" + } } \ No newline at end of file diff --git a/utils/cn.ts b/utils/cn.ts new file mode 100644 index 0000000..242d96f --- /dev/null +++ b/utils/cn.ts @@ -0,0 +1,7 @@ +import type { ClassValue } from "clsx"; +import { clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +}