diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index fc2ec1e..49a3607 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -6,20 +6,19 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set Node Version to 18 + - name: Set Node Version to 22 uses: actions/setup-node@v2 with: - node-version: '18' + node-version: '22' + + - name: Enable Corepack + run: corepack enable - name: Install Dependencies - uses: borales/actions-yarn@v4 - with: - cmd: install + run: yarn install - - name: Build Production Bundle - uses: borales/actions-yarn@v4 - with: - cmd: build + - name: Build + run: yarn build - name: Push to Build Branch if: github.ref == 'refs/heads/main' @@ -30,3 +29,6 @@ jobs: FOLDER: build # The directory where your assets are generated GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # GitHub will automatically add this - you don't need to bother getting a token MESSAGE: 'Build: ({sha}) {msg}' # The commit message + # Env Secrets + PUBLIC_ALGOLIA_ID: ${{ secrets.PUBLIC_ALGOLIA_ID }} + PUBLIC_ALGOLIA_SEARCH_KEY: ${{ secrets.PUBLIC_ALGOLIA_SEARCH_KEY }} diff --git a/.prettierrc b/.prettierrc index da7ed34..71a51b6 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,9 +1,41 @@ { + "$schema": "http://json.schemastore.org/prettierrc", + "tabWidth": 4, "useTabs": true, + "semi": false, "singleQuote": true, - "trailingComma": "none", + "arrowParens": "avoid", "printWidth": 100, - "plugins": ["prettier-plugin-svelte"], - "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }], - "semi": false + "trailingComma": "es5", + "bracketSameLine": false, + "endOfLine": "auto", + "overrides": [ + { + "files": ["**/*.yml", "**/*.yaml"], + "options": { + "parser": "yaml", + "tabWidth": 2, + "useTabs": false + } + }, + { + "files": "**/*.ts", + "options": { + "parser": "typescript" + } + }, + { + "files": "**/*.svelte", + "options": { + "parser": "svelte", + "plugins": ["prettier-plugin-svelte"] + } + }, + { + "files": "**/*.svg", + "options": { + "parser": "html" + } + } + ] } diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz new file mode 100644 index 0000000..6fd7298 Binary files /dev/null and b/.yarn/install-state.gz differ diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/eslint.config.js b/eslint.config.js deleted file mode 100644 index 5a7eb2c..0000000 --- a/eslint.config.js +++ /dev/null @@ -1,33 +0,0 @@ -import js from '@eslint/js' -import ts from 'typescript-eslint' -import svelte from 'eslint-plugin-svelte' -import prettier from 'eslint-config-prettier' -import globals from 'globals' - -/** @type {import('eslint').Linter.FlatConfig[]} */ -export default [ - js.configs.recommended, - ...ts.configs.recommended, - ...svelte.configs['flat/recommended'], - prettier, - ...svelte.configs['flat/prettier'], - { - languageOptions: { - globals: { - ...globals.browser, - ...globals.node - } - } - }, - { - files: ['**/*.svelte'], - languageOptions: { - parserOptions: { - parser: ts.parser - } - } - }, - { - ignores: ['build/', '.svelte-kit/', 'dist/'] - } -] diff --git a/eslint.config.ts b/eslint.config.ts new file mode 100644 index 0000000..a0a29b1 --- /dev/null +++ b/eslint.config.ts @@ -0,0 +1,266 @@ +import svelteEslint from 'eslint-plugin-svelte' +import svelteParser from 'svelte-eslint-parser' +import tsESLint, { type ConfigWithExtends } from 'typescript-eslint' +import svelteConfig from './svelte.config' +import type { NamingConventionRule } from './tslintNamingConventionRule' + +console.log(`[${new Date().toLocaleTimeString()}] Loading ESLint config`) + +const IGNORE_PATTERNS = [ + '.DS_Store', + '.env', + '.env.*', + '.github', + '.vscode', + '**/node_modules/**', + + // Blockbench Plugin Template + 'dist/**/*', + + // Ignore files for PNPM, NPM and YARN + 'pnpm-lock.yaml', + 'package-lock.json', + 'yarn.lock' +] + +const CUSTOM_RULES: ConfigWithExtends['rules'] = { + // ESLint + semi: ['error', 'never'], + 'prefer-const': 'warn', + 'no-fallthrough': 'off', + 'no-mixed-spaces-and-tabs': 'off', + 'no-unreachable': 'warn', + '@typescript-eslint/no-unused-vars': [ + 'warn', + { + vars: 'local', + args: 'after-used', + argsIgnorePattern: '^_', + ignoreRestSiblings: true + } + ], + // Svelte + 'svelte/html-quotes': ['warn', { prefer: 'double' }], + 'svelte/block-lang': ['error', { script: ['ts', null], style: null }], + 'svelte/comment-directive': ['error', { reportUnusedDisableDirectives: true }], + // Check File + 'check-file/filename-naming-convention': [ + 'error', + { + 'src/**/*.{ts.d.ts}': 'CAMEL_CASE', + 'tools/**/*.{ts.d.ts}': 'CAMEL_CASE' + } + ], + 'check-file/folder-naming-convention': [ + 'error', + { + 'src/**': 'KEBAB_CASE', + 'tools/**': 'KEBAB_CASE' + } + ], + // TypeScript + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }], + '@typescript-eslint/array-type': ['warn', { default: 'array-simple' }], + '@typescript-eslint/consistent-indexed-object-style': ['warn', 'record'], + '@typescript-eslint/consistent-generic-constructors': 'warn', + '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/restrict-template-expressions': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/require-await': 'warn', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/unbound-method': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/triple-slash-reference': 'off', + // Naming conventions + '@typescript-eslint/naming-convention': [ + 'warn', + { + // DFU Version imports + selector: ['import'], + modifiers: ['default'], + filter: { + regex: 'v\\d+_\\d+_\\d+$', + match: true + }, + custom: { + match: true, + regex: 'v\\d+_\\d+_\\d+$' + }, + format: null + }, + { + selector: ['import'], + modifiers: ['default'], + format: ['camelCase', 'PascalCase', 'UPPER_CASE'] + }, + { + selector: 'class', + format: ['PascalCase'] + }, + { + selector: ['classProperty', 'classMethod'], + format: ['camelCase'] + }, + { + selector: ['classProperty', 'classMethod'], + leadingUnderscore: 'allow', + format: ['camelCase'] + }, + { + selector: ['classProperty', 'classMethod'], + modifiers: ['private'], + leadingUnderscore: 'allowDouble', + trailingUnderscore: 'allowDouble', + format: ['camelCase'] + }, + { + selector: 'typeProperty', + format: null + }, + { + selector: 'variable', + modifiers: ['const', 'destructured'], + format: null + }, + { + selector: 'variable', + modifiers: ['const', 'global'], + types: ['function'], + leadingUnderscore: 'allow', + format: ['UPPER_CASE', 'camelCase'] + }, + { + selector: 'variable', + modifiers: ['const', 'global'], + leadingUnderscore: 'allow', + format: ['UPPER_CASE'] + }, + { + selector: 'variable', + modifiers: ['const', 'exported'], + format: ['camelCase', 'UPPER_CASE'] + }, + { + selector: 'variableLike', + format: ['camelCase'] + }, + { selector: 'interface', format: ['PascalCase'] }, + { + selector: 'interface', + modifiers: ['exported'], + format: ['PascalCase'], + prefix: ['I'] + }, + { selector: 'typeLike', format: ['PascalCase'] }, + { selector: 'objectLiteralProperty', format: null }, + { selector: 'default', format: ['camelCase'] }, + { + selector: 'parameter', + modifiers: ['unused'], + format: ['camelCase'], + leadingUnderscore: 'allow' + }, + { + selector: 'parameter', + format: ['camelCase'] + }, + { + selector: 'enumMember', + format: ['camelCase', 'PascalCase', 'UPPER_CASE'] + }, + { + selector: 'enum', + format: ['UPPER_CASE'] + } + ] satisfies NamingConventionRule +} + +export default tsESLint.config( + { + ignores: IGNORE_PATTERNS + }, + ...tsESLint.configs.stylisticTypeChecked, + ...svelteEslint.configs['flat/prettier'], + { + plugins: { + '@typescript-eslint': tsESLint.plugin, + svelte: svelteEslint + } + }, + { + rules: CUSTOM_RULES + }, + { + languageOptions: { + parser: tsESLint.parser, + parserOptions: { + project: './tsconfig.json', + extraFileExtensions: ['.svelte'] + }, + globals: { + browser: true, + node: true + } + } + }, + { + files: ['**/*.svelte'], + rules: { + // Causes issues with Svelte and global types + 'no-undef': 'off', + '@typescript-eslint/naming-convention': [ + 'warn', + { + selector: 'variable', + modifiers: ['exported'], + format: ['camelCase'] + }, + { + selector: 'variable', + modifiers: ['const', 'global'], + format: ['UPPER_CASE'] + }, + { + selector: 'variable', + modifiers: ['const', 'global'], + types: ['function'], + format: ['camelCase'] + }, + { + selector: 'variable', + format: ['camelCase'], + leadingUnderscore: 'allow' + } + ] satisfies NamingConventionRule + }, + languageOptions: { + parser: svelteParser, + parserOptions: { + parser: tsESLint.parser, + svelteConfig: svelteConfig, + extraFileExtensions: ['.svelte'] + }, + globals: { + browser: true, + node: true + } + }, + settings: { + ignoreWarnings: ['svelte/a11y-no-onchange', 'a11y-no-onchange'] + } + }, + { + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: '.' + } + }, + linterOptions: { + reportUnusedDisableDirectives: true + } + } +) diff --git a/package.json b/package.json index 4e6e08c..906a50c 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,9 @@ "format": "prettier --write ." }, "devDependencies": { + "@algolia/client-search": "^5.20.3", + "@docsearch/css": "^3.9.0", + "@docsearch/js": "^3.9.0", "@iconify-json/ri": "^1.0.0", "@iconify/json": "^2.2.219", "@sveltejs/adapter-auto": "^3.0.0", @@ -20,19 +23,24 @@ "@sveltejs/vite-plugin-svelte": "^3.0.0", "@svelteness/kit-docs": "^1.1.5", "@types/eslint": "^8.56.7", + "@types/react": "^19.0.10", "clsx": "^1.0.0", - "eslint": "^9.0.0", + "eslint": "^9.21.0", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-svelte": "^2.36.0", + "eslint-plugin-svelte": "^3.0.2", "globals": "^15.0.0", + "jiti": "^2.4.2", "prettier": "^3.1.1", "prettier-plugin-svelte": "^3.1.2", + "react": "^19.0.0", + "react-dom": "^19.0.0", "shiki": "^0.12.0", "svelte": "^4.2.7", "svelte-check": "^3.6.0", + "svelte-eslint-parser": "^1.0.0", "tslib": "^2.4.1", "typescript": "^5.0.0", - "typescript-eslint": "^8.0.0-alpha.20", + "typescript-eslint": "^8.25.0", "unplugin-icons": "^0.19.0", "vite": "^5.0.3" }, @@ -40,5 +48,5 @@ "dependencies": { "svelte-youtube-embed": "^0.3.0" }, - "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" + "packageManager": "yarn@4.7.0+sha512.5a0afa1d4c1d844b3447ee3319633797bcd6385d9a44be07993ae52ff4facabccafb4af5dcd1c2f9a94ac113e5e9ff56f6130431905884414229e284e37bb7c9" } diff --git a/src/app.d.ts b/src/app.d.ts index efabaec..dc1e9d2 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,3 +1,4 @@ +/// /// import type { CompositionEventHandler } from 'svelte/elements' diff --git a/src/app.html b/src/app.html index ee6c4e5..8e4913e 100644 --- a/src/app.html +++ b/src/app.html @@ -5,19 +5,6 @@ - - + + + {#key $page.url.pathname} + {#if title} + {title} + {/if} + {#if description} + + {/if} + + + + + + + {/key} + diff --git a/src/components/VideoCarousel.svelte b/src/components/VideoCarousel.svelte index 998c97e..9e0c93e 100644 --- a/src/components/VideoCarousel.svelte +++ b/src/components/VideoCarousel.svelte @@ -1,4 +1,5 @@ @@ -24,7 +25,7 @@ '-6pLFvW5_Dk', // Camera Animation Test 'sC3FqzpDrpQ', // Diamond Golem 'fs0NUGmsa10', // Butter Dog - '4VlwyI0EHo4' // Funny Mobile Game + '4VlwyI0EHo4', // Funny Mobile Game ] async function getYoutubeVideoTitle(id: string) { @@ -50,10 +51,8 @@ {:catch error} {/await} -
- {#each VIDEOS as id} + {#each VIDEOS as id, index}
+
{ + if ( + e.target.parentElement?.parentElement?.parentElement?.classList.contains( + 'transition-in' + ) + ) + return + e.target.parentElement.parentElement.parentElement.classList.add( + 'transition-in' + ) + }} + >
+ {#await getYoutubeVideoTitle(id)}

Loading Title...

{:then title} @@ -102,6 +116,15 @@ display: flex; flex-direction: column; align-items: center; + transition: + transform 0.2s ease 0s, + opacity 0.2s ease 0s; + transform: translateY(16px) scale(0.9); + opacity: 0; + } + .thumbnail-container:global(.transition-in) { + transform: translateY(0) scale(1); + opacity: 1; } .thumbnail-container :global(button span) { display: flex; diff --git a/src/kit-docs/WigglyText.svelte b/src/kit-docs/WigglyText.svelte index 65969cc..f13eed5 100644 --- a/src/kit-docs/WigglyText.svelte +++ b/src/kit-docs/WigglyText.svelte @@ -27,4 +27,10 @@ animation: wave 1.5s infinite; animation-timing-function: ease-in-out; } + + @media (prefers-reduced-motion: reduce) { + .wiggly-text { + animation: none; + } + } diff --git a/src/kit-docs/kofiLogo.svelte b/src/kit-docs/kofiLogo.svelte new file mode 100644 index 0000000..d8a2c88 --- /dev/null +++ b/src/kit-docs/kofiLogo.svelte @@ -0,0 +1,8 @@ + + Ko-fi + + diff --git a/src/lib/inView.ts b/src/lib/inView.ts index cb26ece..fbcea18 100644 --- a/src/lib/inView.ts +++ b/src/lib/inView.ts @@ -4,10 +4,10 @@ interface ISetObserverOptions { bottom?: number } -export default function inView(node: HTMLElement, options: ISetObserverOptions = {}) { +export function inView(node: HTMLElement, options: ISetObserverOptions = {}) { let observer: IntersectionObserver - const handleIntersect: IntersectionObserverCallback = (e) => { + const handleIntersect: IntersectionObserverCallback = e => { const v = e[0].isIntersecting ? 'enter' : 'exit' node.dispatchEvent(new CustomEvent(v)) } @@ -31,6 +31,6 @@ export default function inView(node: HTMLElement, options: ISetObserverOptions = destroy() { if (observer) observer.disconnect() - } + }, } } diff --git a/src/lib/index.ts b/src/lib/index.ts index b2decee..fa9efd3 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,2 +1,3 @@ // place files you want to import through the `$lib` alias in this folder. -export { default as inView } from './inView' +export * from './scrollAnim' +export * from './inView' diff --git a/src/lib/scrollAnim.ts b/src/lib/scrollAnim.ts new file mode 100644 index 0000000..39db2a5 --- /dev/null +++ b/src/lib/scrollAnim.ts @@ -0,0 +1,67 @@ +import { quartOut } from 'svelte/easing' + +interface IScrollAnimatorOptions { + x?: number + yOffset?: number + opacity?: number + scale?: number + rate?: number + easing?: (t: number) => number +} + +export function scrollAnimator(element: HTMLElement, options: IScrollAnimatorOptions = {}) { + const rect = element.getBoundingClientRect() + const docRect = document.body.getBoundingClientRect() + element.style.opacity = '0' + element.style.transition = 'opacity 0.1s ease, transform 0.1s ease' + + const rate = options.rate || 1 + + let top = rect.top - docRect.top - window.innerHeight / 2 + if (options.yOffset) top += options.yOffset + else top += -rect.height / 2 + + const moveX = options.x || 0 + const scale = options.scale + + const updateTransform = (t: number) => { + t = (options.easing ?? quartOut)(t) + const x = -moveX + moveX * t + let s = 1 + if (scale) { + s = scale + (1 - scale) * t + } + element.style.transform = `translateX(${x}px) scale(${s})` + } + + const updateOpacity = (t: number) => { + element.style.opacity = `${Math.min(t * 1.25, 1)}` + } + + const observer = () => { + const scroll = window.scrollY + const localScroll = Math.min(Math.max(scroll - top, 0), rect.height) + const percent = Math.min(Math.max(localScroll / (rect.height * rate), 0), 1) + updateTransform(percent) + updateOpacity(percent) + } + + let intervalID: number | undefined + const setObserver = ({}: IScrollAnimatorOptions) => { + if (intervalID) clearInterval(intervalID) + intervalID = setInterval(observer, 16) + observer() + } + + setObserver(options) + + return { + update(args: IScrollAnimatorOptions) { + setObserver(args) + }, + + destroy() { + if (intervalID) clearInterval(intervalID) + }, + } +} diff --git a/src/lib/strings.ts b/src/lib/strings.ts new file mode 100644 index 0000000..6be0f12 --- /dev/null +++ b/src/lib/strings.ts @@ -0,0 +1 @@ +export const SLOGAN = 'Effortlessly craft complex animations for Minecraft: Java Edition' diff --git a/src/lib/styles/kit-docs.css b/src/lib/styles/kit-docs.css index f92791a..67f4596 100644 --- a/src/lib/styles/kit-docs.css +++ b/src/lib/styles/kit-docs.css @@ -1,20 +1,6 @@ -:root, -.prefers-light-scheme { - --kd-color-brand: 0 172 237; - --kd-color-focus: 79 70 229; - --kd-color-soft: 68 78 94; - --kd-color-body: 250 250 250; - --kd-color-elevate: 243 244 246; - --kd-color-inverse: 5 11 23; - --kd-color-border: 209 213 219; - - --kd-code-fence-bg: rgb(212 217 251); - --kd-code-fence-fg: rgb(26, 29, 36); -} - :root { font-size: 16px; - scroll-padding-top: 6rem; + scroll-padding-top: 8rem; --kd-font-family-sans: Inter VF, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', @@ -64,18 +50,18 @@ } } -:root.dark, -.prefers-dark-scheme { +:root { color-scheme: dark; + --kd-color-focus: 0 172 237 !important; --kd-color-brand: 0 172 237; --kd-color-soft: 181 186 199; --kd-color-inverse: 245 245 245; --kd-color-border: 38 48 64; - --kd-color-elevate: 43 44 50; + --kd-color-elevate: 48 52 65; --kd-color-body: 31 33 40; --kd-color-focus: 129 140 248; - --kd-color-subtle: rgb(106, 116, 132); + --kd-color-subtle: rgb(144 156 176); --kd-code-fence-bg: rgb(22 23 27); --kd-code-fence-fg: rgb(186 190 216); @@ -96,6 +82,14 @@ code::after { content: '' !important; } +.code-fence { + border-radius: 4px 4px 4px 4px; +} + +.kit-docs .code-fence .code pre { + margin-bottom: 4px; +} + code:not(pre code) { background-color: #16171b !important; padding: 4px 6px !important; @@ -106,6 +100,81 @@ code:not(pre code) { text-wrap: nowrap !important; } +a.link { + color: var(--tw-prose-links) !important; + text-decoration: none; + font-weight: 400; + border-bottom: 1px solid rgb(var(--kd-color-brand)); +} + td { text-wrap: balance !important; } + +p > img { + max-width: 75% !important; +} + +#main-sidebar { + width: 64px !important; + max-width: 64px !important; +} + +.on-this-page:not(:has(*)) { + display: none !important; +} +.on-this-page { + width: 25%; + padding-left: 1rem; +} +.on-this-page li { + list-style: disc; + font-size: small; +} + +* { + scrollbar-width: unset !important; +} + +.scrollbar::-webkit-scrollbar { + width: 8px !important; + height: 8px !important; +} +.scrollbar::-webkit-scrollbar-thumb { + background-color: rgb(var(--kd-color-brand)) !important; + border-radius: 2px !important; +} +.scrollbar::-webkit-scrollbar-track { + background-color: rgb(var(--kd-color-elevate)) !important; + border-radius: 2px !important; +} + +::-webkit-scrollbar { + width: 8px !important; + height: 8px !important; +} + +::-webkit-scrollbar-thumb { + background-color: rgb(var(--kd-color-brand)) !important; + border-radius: 2px !important; +} + +::-webkit-scrollbar-track { + background-color: rgb(var(--kd-color-elevate)) !important; + border-radius: 2px !important; + margin: 0 4px !important; +} + +::-webkit-scrollbar-thumb:hover { + background-color: rgb(var(--kd-color-brand)) !important; +} + +table { + box-shadow: inset 0 0 0 1px rgb(var(--kd-color-border)); + background-color: #1c1e24; +} + +th { + padding: 0.3rem 0.6rem !important; + vertical-align: middle !important; +} diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte new file mode 100644 index 0000000..3d7cdc1 --- /dev/null +++ b/src/routes/+error.svelte @@ -0,0 +1,7 @@ + diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index b491f01..03bbdda 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,73 +1,121 @@ - - {#key $page.url.pathname} - {#if title} - {title} - {/if} - {#if description} - - {/if} - - - - - + + + + - - {/key} - - - + + +
+ + +