diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 938239217..000000000 --- a/.prettierignore +++ /dev/null @@ -1,4 +0,0 @@ -# Add files here to ignore them from prettier formatting -/dist -/coverage -/.turbo \ No newline at end of file diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index ae07ef4ad..000000000 --- a/.prettierrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "printWidth": 100, - "semi": false, - "singleQuote": true, - "endOfLine": "auto" -} diff --git a/apps/frontend/.eslintrc.json b/apps/frontend/.eslintrc.json index 4a1f670ba..52fb09d51 100644 --- a/apps/frontend/.eslintrc.json +++ b/apps/frontend/.eslintrc.json @@ -1,6 +1,10 @@ { - "extends": ["@nuxt/eslint-config"], - "ignorePatterns": ["!**/*", ".nuxt/**", ".output/**", "node_modules"], + "extends": ["@nuxt/eslint-config", "plugin:prettier/recommended", "prettier"], + "env": { + "browser": true, + "node": true + }, + "ignorePatterns": [".nuxt/**", ".output/**", "node_modules"], "overrides": [ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.vue"], diff --git a/apps/frontend/.prettierignore b/apps/frontend/.prettierignore new file mode 100644 index 000000000..951dbd850 --- /dev/null +++ b/apps/frontend/.prettierignore @@ -0,0 +1,3 @@ +**/.nuxt +**/dist +**/node_modules \ No newline at end of file diff --git a/apps/frontend/.prettierrc b/apps/frontend/.prettierrc new file mode 100644 index 000000000..8c662d196 --- /dev/null +++ b/apps/frontend/.prettierrc @@ -0,0 +1,4 @@ +{ + "endOfLine": "auto", + "plugins": ["prettier-plugin-tailwindcss"] +} diff --git a/apps/frontend/COPYING.md b/apps/frontend/COPYING.md index 2c2a5fd28..991db6431 100644 --- a/apps/frontend/COPYING.md +++ b/apps/frontend/COPYING.md @@ -12,7 +12,7 @@ This includes, but may not be limited to, the following files: - assets/images/404.svg - assets/images/logo.svg -- components/brand/* +- components/brand/\* - static/favicon.ico - static/favicon-light.ico @@ -20,4 +20,4 @@ This includes, but may not be limited to, the following files: The following files are owned by their respective copyright holders and must be used within each of their Brand Guidelines: -- assets/images/external/* +- assets/images/external/\* diff --git a/apps/frontend/crowdin.yml b/apps/frontend/crowdin.yml index 2817cde42..e7c47c7da 100644 --- a/apps/frontend/crowdin.yml +++ b/apps/frontend/crowdin.yml @@ -1,6 +1,6 @@ project_id: 518556 preserve_hierarchy: true -commit_message: '[ci skip]' +commit_message: "[ci skip]" files: - source: /locales/en-US/* diff --git a/apps/frontend/nuxt.config.ts b/apps/frontend/nuxt.config.ts index d3a2703ce..b0ccc4d47 100644 --- a/apps/frontend/nuxt.config.ts +++ b/apps/frontend/nuxt.config.ts @@ -1,27 +1,28 @@ -import { promises as fs } from 'fs' -import { pathToFileURL } from 'node:url' -import svgLoader from 'vite-svg-loader' -import { resolve, basename, relative } from 'pathe' -import { defineNuxtConfig } from 'nuxt/config' -import { $fetch } from 'ofetch' -import { globIterate } from 'glob' -import { match as matchLocale } from '@formatjs/intl-localematcher' -import { consola } from 'consola' - -const STAGING_API_URL = 'https://staging-api.modrinth.com/v2/' +/* eslint-disable no-extra-semi */ +import { promises as fs } from "fs"; +import { pathToFileURL } from "node:url"; +import svgLoader from "vite-svg-loader"; +import { resolve, basename, relative } from "pathe"; +import { defineNuxtConfig } from "nuxt/config"; +import { $fetch } from "ofetch"; +import { globIterate } from "glob"; +import { match as matchLocale } from "@formatjs/intl-localematcher"; +import { consola } from "consola"; + +const STAGING_API_URL = "https://staging-api.modrinth.com/v2/"; const preloadedFonts = [ - 'inter/Inter-Regular.woff2', - 'inter/Inter-Medium.woff2', - 'inter/Inter-SemiBold.woff2', - 'inter/Inter-Bold.woff2', -] + "inter/Inter-Regular.woff2", + "inter/Inter-Medium.woff2", + "inter/Inter-SemiBold.woff2", + "inter/Inter-Bold.woff2", +]; const favicons = { - '(prefers-color-scheme:no-preference)': '/favicon-light.ico', - '(prefers-color-scheme:light)': '/favicon-light.ico', - '(prefers-color-scheme:dark)': '/favicon.ico', -} + "(prefers-color-scheme:no-preference)": "/favicon-light.ico", + "(prefers-color-scheme:light)": "/favicon-light.ico", + "(prefers-color-scheme:dark)": "/favicon.ico", +}; /** * Tags of locales that are auto-discovered besides the default locale. @@ -29,67 +30,67 @@ const favicons = { * Preferably only the locales that reach a certain threshold of complete * translations would be included in this array. */ -const enabledLocales: string[] = [] +const enabledLocales: string[] = []; /** * Overrides for the categories of the certain locales. */ -const localesCategoriesOverrides: Partial> = { - 'en-x-pirate': 'fun', - 'en-x-updown': 'fun', - 'en-x-lolcat': 'fun', - 'en-x-uwu': 'fun', - 'ru-x-bandit': 'fun', - ar: 'experimental', - he: 'experimental', - pes: 'experimental', -} +const localesCategoriesOverrides: Partial> = { + "en-x-pirate": "fun", + "en-x-updown": "fun", + "en-x-lolcat": "fun", + "en-x-uwu": "fun", + "ru-x-bandit": "fun", + ar: "experimental", + he: "experimental", + pes: "experimental", +}; export default defineNuxtConfig({ - srcDir: 'src/', + srcDir: "src/", app: { head: { htmlAttrs: { - lang: 'en', + lang: "en", }, - title: 'Modrinth', + title: "Modrinth", link: [ // The type is necessary because the linter can't always compare this very nested/complex type on itself ...preloadedFonts.map((font): object => { return { - rel: 'preload', + rel: "preload", href: `https://cdn-raw.modrinth.com/fonts/${font}?v=3.19`, - as: 'font', - type: 'font/woff2', - crossorigin: 'anonymous', - } + as: "font", + type: "font/woff2", + crossorigin: "anonymous", + }; }), ...Object.entries(favicons).map(([media, href]): object => { - return { rel: 'icon', type: 'image/x-icon', href, media } + return { rel: "icon", type: "image/x-icon", href, media }; }), ...Object.entries(favicons).map(([media, href]): object => { - return { rel: 'apple-touch-icon', type: 'image/x-icon', href, media, sizes: '64x64' } + return { rel: "apple-touch-icon", type: "image/x-icon", href, media, sizes: "64x64" }; }), { - rel: 'search', - type: 'application/opensearchdescription+xml', - href: '/opensearch.xml', - title: 'Modrinth mods', + rel: "search", + type: "application/opensearchdescription+xml", + href: "/opensearch.xml", + title: "Modrinth mods", }, ], }, }, vite: { - cacheDir: '../../node_modules/.vite/apps/knossos', + cacheDir: "../../node_modules/.vite/apps/knossos", resolve: { - dedupe: ['vue'], + dedupe: ["vue"], }, plugins: [ svgLoader({ svgoConfig: { plugins: [ { - name: 'preset-default', + name: "preset-default", params: { overrides: { removeViewBox: false, @@ -102,28 +103,28 @@ export default defineNuxtConfig({ ], }, hooks: { - async 'build:before'() { + async "build:before"() { // 30 minutes - const TTL = 30 * 60 * 1000 + const TTL = 30 * 60 * 1000; let state: { - lastGenerated?: string - apiUrl?: string - categories?: any[] - loaders?: any[] - gameVersions?: any[] - donationPlatforms?: any[] - reportTypes?: any[] - } = {} + lastGenerated?: string; + apiUrl?: string; + categories?: any[]; + loaders?: any[]; + gameVersions?: any[]; + donationPlatforms?: any[]; + reportTypes?: any[]; + } = {}; try { - state = JSON.parse(await fs.readFile('./src/generated/state.json', 'utf8')) + state = JSON.parse(await fs.readFile("./src/generated/state.json", "utf8")); } catch { // File doesn't exist, create folder - await fs.mkdir('./src/generated', { recursive: true }) + await fs.mkdir("./src/generated", { recursive: true }); } - const API_URL = getApiUrl() + const API_URL = getApiUrl(); if ( // Skip regeneration if within TTL... @@ -132,18 +133,18 @@ export default defineNuxtConfig({ // ...but only if the API URL is the same state.apiUrl === API_URL ) { - return + return; } - state.lastGenerated = new Date().toISOString() + state.lastGenerated = new Date().toISOString(); - state.apiUrl = API_URL + state.apiUrl = API_URL; const headers = { headers: { - 'user-agent': 'Knossos generator (support@modrinth.com)', + "user-agent": "Knossos generator (support@modrinth.com)", }, - } + }; const [categories, loaders, gameVersions, donationPlatforms, reportTypes] = await Promise.all( [ @@ -152,137 +153,137 @@ export default defineNuxtConfig({ $fetch(`${API_URL}tag/game_version`, headers), $fetch(`${API_URL}tag/donation_platform`, headers), $fetch(`${API_URL}tag/report_type`, headers), - ] - ) + ], + ); - state.categories = categories - state.loaders = loaders - state.gameVersions = gameVersions - state.donationPlatforms = donationPlatforms - state.reportTypes = reportTypes + state.categories = categories; + state.loaders = loaders; + state.gameVersions = gameVersions; + state.donationPlatforms = donationPlatforms; + state.reportTypes = reportTypes; - await fs.writeFile('./src/generated/state.json', JSON.stringify(state)) + await fs.writeFile("./src/generated/state.json", JSON.stringify(state)); - console.log('Tags generated!') + console.log("Tags generated!"); }, - 'pages:extend'(routes) { + "pages:extend"(routes) { routes.splice( - routes.findIndex((x) => x.name === 'search-searchProjectType'), - 1 - ) + routes.findIndex((x) => x.name === "search-searchProjectType"), + 1, + ); - const types = ['mods', 'modpacks', 'plugins', 'resourcepacks', 'shaders', 'datapacks'] + const types = ["mods", "modpacks", "plugins", "resourcepacks", "shaders", "datapacks"]; types.forEach((type) => routes.push({ name: `search-${type}`, path: `/${type}`, - file: resolve(__dirname, 'src/pages/search/[searchProjectType].vue'), + file: resolve(__dirname, "src/pages/search/[searchProjectType].vue"), children: [], - }) - ) + }), + ); }, - async 'vintl:extendOptions'(opts) { - opts.locales ??= [] + async "vintl:extendOptions"(opts) { + opts.locales ??= []; - const isProduction = getDomain() === 'https://modrinth.com' + const isProduction = getDomain() === "https://modrinth.com"; const resolveCompactNumberDataImport = await (async () => { - const compactNumberLocales: string[] = [] + const compactNumberLocales: string[] = []; for await (const localeFile of globIterate( - 'node_modules/@vintl/compact-number/dist/locale-data/*.mjs', - { ignore: '**/*.data.mjs' } + "node_modules/@vintl/compact-number/dist/locale-data/*.mjs", + { ignore: "**/*.data.mjs" }, )) { - const tag = basename(localeFile, '.mjs') - compactNumberLocales.push(tag) + const tag = basename(localeFile, ".mjs"); + compactNumberLocales.push(tag); } function resolveImport(tag: string) { - const matchedTag = matchLocale([tag], compactNumberLocales, 'en-x-placeholder') - return matchedTag === 'en-x-placeholder' + const matchedTag = matchLocale([tag], compactNumberLocales, "en-x-placeholder"); + return matchedTag === "en-x-placeholder" ? undefined - : `@vintl/compact-number/locale-data/${matchedTag}` + : `@vintl/compact-number/locale-data/${matchedTag}`; } - return resolveImport - })() + return resolveImport; + })(); const resolveOmorphiaLocaleImport = await (async () => { - const omorphiaLocales: string[] = [] - const omorphiaLocaleSets = new Map() + const omorphiaLocales: string[] = []; + const omorphiaLocaleSets = new Map(); - for await (const localeDir of globIterate('node_modules/omorphia/locales/*', { + for await (const localeDir of globIterate("node_modules/omorphia/locales/*", { posix: true, })) { - const tag = basename(localeDir) - omorphiaLocales.push(tag) + const tag = basename(localeDir); + omorphiaLocales.push(tag); - const localeFiles: { from: string; format?: string }[] = [] + const localeFiles: { from: string; format?: string }[] = []; - omorphiaLocaleSets.set(tag, { files: localeFiles }) + omorphiaLocaleSets.set(tag, { files: localeFiles }); for await (const localeFile of globIterate(`${localeDir}/*`, { posix: true })) { localeFiles.push({ from: pathToFileURL(localeFile).toString(), - format: 'default', - }) + format: "default", + }); } } return function resolveLocaleImport(tag: string) { - return omorphiaLocaleSets.get(matchLocale([tag], omorphiaLocales, 'en-x-placeholder')) - } - })() + return omorphiaLocaleSets.get(matchLocale([tag], omorphiaLocales, "en-x-placeholder")); + }; + })(); - for await (const localeDir of globIterate('src/locales/*/', { posix: true })) { - const tag = basename(localeDir) - if (isProduction && !enabledLocales.includes(tag) && opts.defaultLocale !== tag) continue + for await (const localeDir of globIterate("src/locales/*/", { posix: true })) { + const tag = basename(localeDir); + if (isProduction && !enabledLocales.includes(tag) && opts.defaultLocale !== tag) continue; const locale = opts.locales.find((locale) => locale.tag === tag) ?? - opts.locales[opts.locales.push({ tag }) - 1]! + opts.locales[opts.locales.push({ tag }) - 1]!; - const localeFiles = (locale.files ??= []) + const localeFiles = (locale.files ??= []); for await (const localeFile of globIterate(`${localeDir}/*`, { posix: true })) { - const fileName = basename(localeFile) - if (fileName === 'index.json') { + const fileName = basename(localeFile); + if (fileName === "index.json") { localeFiles.push({ - from: `./${relative('./src', localeFile)}`, - format: 'crowdin', - }) - } else if (fileName === 'meta.json') { + from: `./${relative("./src", localeFile)}`, + format: "crowdin", + }); + } else if (fileName === "meta.json") { const meta: Record = await fs - .readFile(localeFile, 'utf8') - .then((date) => JSON.parse(date)) - const localeMeta = (locale.meta ??= {}) + .readFile(localeFile, "utf8") + .then((date) => JSON.parse(date)); + const localeMeta = (locale.meta ??= {}); for (const key in meta) { - const value = meta[key] - if (value === undefined) continue - localeMeta[key] = value.message + const value = meta[key]; + if (value === undefined) continue; + localeMeta[key] = value.message; } } else { - ;(locale.resources ??= {})[fileName] = `./${relative('./src', localeFile)}` + (locale.resources ??= {})[fileName] = `./${relative("./src", localeFile)}`; } } - const categoryOverride = localesCategoriesOverrides[tag] + const categoryOverride = localesCategoriesOverrides[tag]; if (categoryOverride != null) { - ;(locale.meta ??= {}).category = categoryOverride + (locale.meta ??= {}).category = categoryOverride; } - const omorphiaLocaleData = resolveOmorphiaLocaleImport(tag) + const omorphiaLocaleData = resolveOmorphiaLocaleImport(tag); if (omorphiaLocaleData != null) { - localeFiles.push(...omorphiaLocaleData.files) + localeFiles.push(...omorphiaLocaleData.files); } - const cnDataImport = resolveCompactNumberDataImport(tag) + const cnDataImport = resolveCompactNumberDataImport(tag); if (cnDataImport != null) { - ;(locale.additionalImports ??= []).push({ + (locale.additionalImports ??= []).push({ from: cnDataImport, resolve: false, - }) + }); } } }, @@ -298,22 +299,22 @@ export default defineNuxtConfig({ production: isProduction(), featureFlagOverrides: getFeatureFlagOverrides(), - owner: process.env.VERCEL_GIT_REPO_OWNER || 'modrinth', - slug: process.env.VERCEL_GIT_REPO_SLUG || 'knossos', + owner: process.env.VERCEL_GIT_REPO_OWNER || "modrinth", + slug: process.env.VERCEL_GIT_REPO_SLUG || "knossos", branch: process.env.VERCEL_GIT_COMMIT_REF || process.env.CF_PAGES_BRANCH || // @ts-ignore globalThis.CF_PAGES_BRANCH || - 'master', + "master", hash: process.env.VERCEL_GIT_COMMIT_SHA || process.env.CF_PAGES_COMMIT_SHA || // @ts-ignore globalThis.CF_PAGES_COMMIT_SHA || - 'unknown', + "unknown", - turnstile: { siteKey: '0x4AAAAAAAW3guHM6Eunbgwu' }, + turnstile: { siteKey: "0x4AAAAAAAW3guHM6Eunbgwu" }, }, }, typescript: { @@ -322,99 +323,106 @@ export default defineNuxtConfig({ typeCheck: false, tsConfig: { compilerOptions: { - moduleResolution: 'bundler', + moduleResolution: "bundler", allowImportingTsExtensions: true, }, }, }, - modules: ['@vintl/nuxt', '@nuxtjs/turnstile'], + modules: ["@vintl/nuxt", "@nuxtjs/turnstile"], vintl: { - defaultLocale: 'en-US', + defaultLocale: "en-US", locales: [ { - tag: 'en-US', + tag: "en-US", meta: { static: { - iso: 'en', + iso: "en", }, }, }, ], - storage: 'cookie', - parserless: 'only-prod', + storage: "cookie", + parserless: "only-prod", seo: { defaultLocaleHasParameter: false, }, onParseError({ error, message, messageId, moduleId, parseMessage, parserOptions }) { - const errorMessage = String(error) - const modulePath = relative(__dirname, moduleId) + const errorMessage = String(error); + const modulePath = relative(__dirname, moduleId); try { - const fallback = parseMessage(message, { ...parserOptions, ignoreTag: true }) + const fallback = parseMessage(message, { ...parserOptions, ignoreTag: true }); consola.warn( - `[i18n] ${messageId} in ${modulePath} cannot be parsed normally due to ${errorMessage}. The tags will will not be parsed.` - ) + `[i18n] ${messageId} in ${modulePath} cannot be parsed normally due to ${errorMessage}. The tags will will not be parsed.`, + ); - return fallback + return fallback; } catch (err) { - const secondaryErrorMessage = String(err) + const secondaryErrorMessage = String(err); const reason = errorMessage === secondaryErrorMessage ? errorMessage - : `${errorMessage} and ${secondaryErrorMessage}` + : `${errorMessage} and ${secondaryErrorMessage}`; consola.warn( - `[i18n] ${messageId} in ${modulePath} cannot be parsed due to ${reason}. It will be skipped.` - ) + `[i18n] ${messageId} in ${modulePath} cannot be parsed due to ${reason}. It will be skipped.`, + ); } }, }, nitro: { - moduleSideEffects: ['@vintl/compact-number/locale-data'], + moduleSideEffects: ["@vintl/compact-number/locale-data"], output: { - dir: '../../dist/apps/knossos/.output', + dir: "../../dist/apps/knossos/.output", }, }, devtools: { enabled: true, }, - compatibilityDate: '2024-07-03', -}) + css: ["~/assets/styles/tailwind.css"], + postcss: { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, + }, + compatibilityDate: "2024-07-03", +}); function getApiUrl() { // @ts-ignore - return process.env.BROWSER_BASE_URL ?? globalThis.BROWSER_BASE_URL ?? STAGING_API_URL + return process.env.BROWSER_BASE_URL ?? globalThis.BROWSER_BASE_URL ?? STAGING_API_URL; } function isProduction() { - return process.env.NODE_ENV === 'production' + return process.env.NODE_ENV === "production"; } function getFeatureFlagOverrides() { - return JSON.parse(process.env.FLAG_OVERRIDES ?? '{}') + return JSON.parse(process.env.FLAG_OVERRIDES ?? "{}"); } function getDomain() { - if (process.env.NODE_ENV === 'production') { + if (process.env.NODE_ENV === "production") { if (process.env.SITE_URL) { - return process.env.SITE_URL + return process.env.SITE_URL; } // @ts-ignore else if (process.env.CF_PAGES_URL || globalThis.CF_PAGES_URL) { // @ts-ignore - return process.env.CF_PAGES_URL ?? globalThis.CF_PAGES_URL + return process.env.CF_PAGES_URL ?? globalThis.CF_PAGES_URL; } else if (process.env.HEROKU_APP_NAME) { - return `https://${process.env.HEROKU_APP_NAME}.herokuapp.com` + return `https://${process.env.HEROKU_APP_NAME}.herokuapp.com`; } else if (process.env.VERCEL_URL) { - return `https://${process.env.VERCEL_URL}` + return `https://${process.env.VERCEL_URL}`; } else if (getApiUrl() === STAGING_API_URL) { - return 'https://staging.modrinth.com' + return "https://staging.modrinth.com"; } else { - return 'https://modrinth.com' + return "https://modrinth.com"; } } else { - return 'http://localhost:3000' + return "http://localhost:3000"; } } diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 222097a94..17df4b575 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -1,6 +1,7 @@ { "name": "@modrinth/frontend", "private": true, + "type": "module", "scripts": { "build": "nuxi build", "dev": "nuxi dev", @@ -9,31 +10,36 @@ "postinstall": "nuxi prepare", "lint:js": "eslint ./src --ext .js,.vue,.ts", "lint": "npm run lint:js && prettier --check .", - "fix": "eslint . --fix --ext .js,.vue,.ts && prettier --write .", + "fix": "eslint . --fix && prettier --write .", "intl:extract": "formatjs extract \"{,components,composables,layouts,middleware,modules,pages,plugins,utils}/**/*.{vue,ts,tsx,js,jsx,mts,cts,mjs,cjs}\" --ignore '**/*.d.ts' --ignore 'node_modules' --out-file locales/en-US/index.json --format crowdin --preserve-whitespace" }, "devDependencies": { "@formatjs/cli": "^6.1.2", "@nuxt/devtools": "^1.3.3", "@nuxt/eslint-config": "^0.3.13", - "@nuxtjs/eslint-config-typescript": "^12.0.0", + "@nuxtjs/eslint-config-typescript": "^12.1.0", "@nuxtjs/turnstile": "^0.8.0", "@types/node": "^20.1.0", - "@typescript-eslint/eslint-plugin": "^5.59.8", - "@typescript-eslint/parser": "^5.59.8", + "@typescript-eslint/eslint-plugin": "^7.15.0", + "@typescript-eslint/parser": "^7.15.0", "@vintl/compact-number": "^2.0.5", "@vintl/how-ago": "^3.0.1", "@vintl/nuxt": "^1.8.0", + "autoprefixer": "^10.4.19", "eslint": "^8.41.0", - "eslint-config-prettier": "^8.8.0", + "eslint-config-custom": "workspace:*", + "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-typescript": "^3.5.5", "eslint-plugin-import": "^2.27.5", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-vue": "^9.14.1", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-vue": "^9.27.0", "glob": "^10.2.7", "nuxt": "^3.12.3", - "prettier": "^2.8.8", + "postcss": "^8.4.39", + "prettier": "^3.3.2", + "prettier-plugin-tailwindcss": "^0.6.5", "sass": "^1.58.0", + "tailwindcss": "^3.4.4", "typescript": "^5.4.5", "vite-plugin-eslint": "^1.8.1", "vite-svg-loader": "^5.1.0", diff --git a/apps/frontend/src/app.vue b/apps/frontend/src/app.vue index 7b8ef22ad..ebd9fa560 100644 --- a/apps/frontend/src/app.vue +++ b/apps/frontend/src/app.vue @@ -6,6 +6,6 @@ diff --git a/apps/frontend/src/assets/styles/components.scss b/apps/frontend/src/assets/styles/components.scss index 4f6c6f4c0..392a39c57 100644 --- a/apps/frontend/src/assets/styles/components.scss +++ b/apps/frontend/src/assets/styles/components.scss @@ -456,7 +456,7 @@ } &:disabled, - &[disabled='true'] { + &[disabled="true"] { cursor: not-allowed; filter: grayscale(50%); opacity: 0.5; @@ -502,7 +502,7 @@ tr.button-transparent { } &:disabled > *, - &[disabled='true'] > * { + &[disabled="true"] > * { cursor: not-allowed; filter: grayscale(50%); opacity: 0.5; @@ -511,7 +511,10 @@ tr.button-transparent { } .button-within { - transition: opacity 0.5s ease-in-out, filter 0.2s ease-in-out, transform 0.05s ease-in-out, + transition: + opacity 0.5s ease-in-out, + filter 0.2s ease-in-out, + transform 0.05s ease-in-out, outline 0.2s ease-in-out; &:focus-visible:not(&.disabled), @@ -530,7 +533,7 @@ tr.button-transparent { box-shadow: none; &disabled, - &[disabled='true'] { + &[disabled="true"] { cursor: not-allowed; box-shadow: none; } @@ -544,7 +547,9 @@ tr.button-transparent { color: var(--text-color); background-color: var(--background-color); - box-shadow: var(--shadow-inset-sm), 0 0 0 0 transparent; + box-shadow: + var(--shadow-inset-sm), + 0 0 0 0 transparent; border-radius: var(--size-rounded-sm); } @@ -560,7 +565,10 @@ tr.button-transparent { cursor: pointer; width: fit-content; height: fit-content; - transition: opacity 0.5s ease-in-out, filter 0.2s ease-in-out, scale 0.05s ease-in-out, + transition: + opacity 0.5s ease-in-out, + filter 0.2s ease-in-out, + scale 0.05s ease-in-out, outline 0.2s ease-in-out; text-decoration: none; @@ -603,7 +611,9 @@ tr.button-transparent { border-radius: var(--size-rounded-sm); color: var(--text-color); background-color: var(--background-color); - box-shadow: var(--shadow-inset-sm), 0 0 0 0 transparent; + box-shadow: + var(--shadow-inset-sm), + 0 0 0 0 transparent; svg { min-width: 1.25rem; @@ -826,7 +836,7 @@ tr.button-transparent { background: var(--color-button-bg); &:after { - content: ''; + content: ""; position: absolute; top: 7px; left: 7px; @@ -1065,7 +1075,9 @@ button { background: var(--color-button-bg); width: fit-content; border-radius: var(--size-rounded-sm); - box-shadow: var(--shadow-inset-sm), 0 0 0 0 transparent; + box-shadow: + var(--shadow-inset-sm), + 0 0 0 0 transparent; transition: box-shadow 0.1s ease-in-out; overflow: hidden; max-width: 100%; @@ -1099,7 +1111,9 @@ button { &:focus, &:focus-visible, &:focus-within { - box-shadow: inset 0 0 0 transparent, 0 0 0 0.25rem var(--color-brand-shadow); + box-shadow: + inset 0 0 0 transparent, + 0 0 0 0.25rem var(--color-brand-shadow); color: var(--color-button-text-active); } } @@ -1436,15 +1450,15 @@ svg.inline-svg { height: var(--_size, var(--icon-16)) !important; border: 1px solid var(--color-button-border); - &[data-size='32'] { + &[data-size="32"] { --_size: var(--icon-32); } - &[data-shape='circle'] { + &[data-shape="circle"] { border-radius: var(--radius-max) !important; } - &[data-shape='square'] { + &[data-shape="square"] { border-radius: calc(2.25 * (var(--_size) / 16)) !important; } } diff --git a/apps/frontend/src/assets/styles/global.scss b/apps/frontend/src/assets/styles/global.scss index a8b4985f8..1a7b89459 100644 --- a/apps/frontend/src/assets/styles/global.scss +++ b/apps/frontend/src/assets/styles/global.scss @@ -214,8 +214,8 @@ html { --shadow-card: rgba(50, 50, 100, 0.1) 0px 2px 4px 0px; - --landing-maze-bg: url('https://cdn.modrinth.com/landing-new/landing-light.webp'); - --landing-maze-gradient-bg: url('https://cdn.modrinth.com/landing-new/landing-lower-light.webp'); + --landing-maze-bg: url("https://cdn.modrinth.com/landing-new/landing-light.webp"); + --landing-maze-gradient-bg: url("https://cdn.modrinth.com/landing-new/landing-lower-light.webp"); --landing-maze-outer-bg: linear-gradient(180deg, #f0f0f0 0%, #ffffff 100%); --landing-color-heading: #000; @@ -341,9 +341,9 @@ html { --shadow-card: rgba(0, 0, 0, 0.25) 0px 2px 4px 0px; - --landing-maze-bg: url('https://cdn.modrinth.com/landing-new/landing.webp'); + --landing-maze-bg: url("https://cdn.modrinth.com/landing-new/landing.webp"); --landing-maze-gradient-bg: linear-gradient(0deg, #31375f 0%, rgba(8, 14, 55, 0) 100%), - url('https://cdn.modrinth.com/landing-new/landing-lower.webp'); + url("https://cdn.modrinth.com/landing-new/landing-lower.webp"); --landing-maze-outer-bg: linear-gradient(180deg, #06060d 0%, #000000 100%); --landing-color-heading: #fff; @@ -537,18 +537,22 @@ textarea { font-weight: var(--font-weight-medium); border: none; outline: 2px solid transparent; - box-shadow: var(--shadow-inset-sm), 0 0 0 0 transparent; + box-shadow: + var(--shadow-inset-sm), + 0 0 0 0 transparent; transition: box-shadow 0.1s ease-in-out; min-height: 40px; &:focus, &:focus-visible { - box-shadow: inset 0 0 0 transparent, 0 0 0 0.25rem var(--color-brand-shadow); + box-shadow: + inset 0 0 0 transparent, + 0 0 0 0.25rem var(--color-brand-shadow); color: var(--color-button-text-active); } &:disabled, - &[disabled='true'] { + &[disabled="true"] { opacity: 0.6; pointer-events: none; cursor: not-allowed; @@ -565,7 +569,7 @@ textarea { } button, -input[type='button'] { +input[type="button"] { cursor: pointer; border: none; outline: 2px solid transparent; @@ -581,13 +585,13 @@ kbd { font-size: 0.85em !important; } -@import '~/assets/styles/layout.scss'; -@import '~/assets/styles/utils.scss'; -@import '~/assets/styles/components.scss'; +@import "~/assets/styles/layout.scss"; +@import "~/assets/styles/utils.scss"; +@import "~/assets/styles/components.scss"; button:focus-visible, a:focus-visible, -[tabindex='0']:focus-visible { +[tabindex="0"]:focus-visible { outline: 0.25rem solid #ea80ff; border-radius: 0.25rem; } @@ -602,7 +606,10 @@ input { } .button-animation { - transition: opacity 0.5s ease-in-out, filter 0.2s ease-in-out, transform 0.05s ease-in-out, + transition: + opacity 0.5s ease-in-out, + filter 0.2s ease-in-out, + transform 0.05s ease-in-out, outline-width 0.2s ease-in-out; } diff --git a/apps/frontend/src/assets/styles/layout.scss b/apps/frontend/src/assets/styles/layout.scss index a53b487c7..c33bd8f5b 100644 --- a/apps/frontend/src/assets/styles/layout.scss +++ b/apps/frontend/src/assets/styles/layout.scss @@ -42,9 +42,9 @@ padding: 0 0.75rem; grid-template: - 'sidebar' - 'content' - 'info' + "sidebar" + "content" + "info" / 100%; @media screen and (max-width: 1024px) { @@ -81,25 +81,25 @@ column-gap: 0.75rem; grid-template: - 'sidebar content' auto - 'info content' auto - 'dummy content' 1fr + "sidebar content" auto + "info content" auto + "dummy content" 1fr / 20rem 1fr; &.alt-layout { grid-template: - 'content sidebar' auto - 'content info' auto - 'content dummy' 1fr + "content sidebar" auto + "content info" auto + "content dummy" 1fr / 1fr 20rem; } &.no-sidebar { grid-template: - 'header header' auto - 'content content' auto - 'info info' auto - 'dummy dummy' 1fr + "header header" auto + "content content" auto + "info info" auto + "dummy dummy" 1fr / 1fr 1fr; .normal-page__content { diff --git a/apps/frontend/src/assets/styles/tailwind.css b/apps/frontend/src/assets/styles/tailwind.css new file mode 100644 index 000000000..f56d34f77 --- /dev/null +++ b/apps/frontend/src/assets/styles/tailwind.css @@ -0,0 +1,9 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +h1, +h2, +h3 { + @apply font-bold; +} diff --git a/apps/frontend/src/components/brand/TextLogo.vue b/apps/frontend/src/components/brand/TextLogo.vue index 96923c3b4..a942c1401 100644 --- a/apps/frontend/src/components/brand/TextLogo.vue +++ b/apps/frontend/src/components/brand/TextLogo.vue @@ -43,32 +43,32 @@ /> diff --git a/apps/frontend/src/components/ui/CollectionCreateModal.vue b/apps/frontend/src/components/ui/CollectionCreateModal.vue index 249bf2505..fce576e7e 100644 --- a/apps/frontend/src/components/ui/CollectionCreateModal.vue +++ b/apps/frontend/src/components/ui/CollectionCreateModal.vue @@ -4,8 +4,8 @@

Your new collection will be created as a public collection with - {{ projectIds.length > 0 ? projectIds.length : 'no' }} - {{ projectIds.length !== 1 ? 'projects' : 'project' }}. + {{ projectIds.length > 0 ? projectIds.length : "no" }} + {{ projectIds.length !== 1 ? "projects" : "project" }}.