@@ -82,6 +82,12 @@ export default {
&.disabled {
cursor: not-allowed;
}
+
+ &.checked {
+ outline: 2px solid transparent;
+ outline-offset: 4px;
+ border-radius: 0.25rem;
+ }
}
.checkbox {
diff --git a/components/ui/MessageBanner.vue b/components/ui/MessageBanner.vue
index e3ac35ed..6432220b 100644
--- a/components/ui/MessageBanner.vue
+++ b/components/ui/MessageBanner.vue
@@ -24,6 +24,7 @@ const ariaLabelByType = computed(() => `Banner with ${props.messageType} message
border-radius: var(--size-rounded-card);
overflow: hidden;
outline: 2px solid transparent;
+ outline-offset: -2px;
margin-bottom: var(--spacing-card-md);
diff --git a/components/ui/Modal.vue b/components/ui/Modal.vue
index e7e32390..95b0aab5 100644
--- a/components/ui/Modal.vue
+++ b/components/ui/Modal.vue
@@ -119,6 +119,7 @@ export default {
overflow-y: auto;
width: 600px;
pointer-events: auto;
+ outline: 3px solid transparent;
.header {
display: flex;
diff --git a/components/ui/NavRow.vue b/components/ui/NavRow.vue
index a926a64c..f417104f 100644
--- a/components/ui/NavRow.vue
+++ b/components/ui/NavRow.vue
@@ -134,6 +134,12 @@ export default {
&.router-link-exact-active {
color: var(--color-text);
+ &:not(:focus-visible) {
+ outline: 2px solid transparent;
+ outline-offset: 6px;
+ border-radius: 0.25rem;
+ }
+
&::after {
opacity: 1;
}
@@ -157,6 +163,8 @@ export default {
transition: all ease-in-out 0.2s;
border-radius: var(--size-rounded-max);
background-color: var(--color-brand);
+ outline: 2px solid transparent;
+ outline-offset: -2px;
@media (prefers-reduced-motion) {
transition: none !important;
diff --git a/components/ui/NavStack.vue b/components/ui/NavStack.vue
index 88fb1040..bbd9ec21 100644
--- a/components/ui/NavStack.vue
+++ b/components/ui/NavStack.vue
@@ -19,6 +19,10 @@ ul {
list-style-type: none;
margin: 0;
padding: 0;
+
+ > :first-child {
+ margin-top: 0;
+ }
}
li {
diff --git a/components/ui/NavStackItem.vue b/components/ui/NavStackItem.vue
index 24aae857..3feb8367 100644
--- a/components/ui/NavStackItem.vue
+++ b/components/ui/NavStackItem.vue
@@ -70,6 +70,7 @@ export default {
box-shadow: none;
padding: 0;
width: 100%;
+ outline: none;
:where(.nav-link) {
--text-color: var(--color-text);
@@ -94,6 +95,9 @@ export default {
}
&.router-link-exact-active {
+ outline: 2px solid transparent;
+ border-radius: 0.25rem;
+
.nav-content {
color: var(--color-button-text-active);
background-color: var(--color-button-bg);
diff --git a/components/ui/Pagination.vue b/components/ui/Pagination.vue
index 37b4307c..f775cf3e 100644
--- a/components/ui/Pagination.vue
+++ b/components/ui/Pagination.vue
@@ -133,6 +133,7 @@ a {
background: var(--color-brand);
color: var(--color-brand-inverted);
cursor: default;
+ outline: 2px solid transparent;
}
&.paginate.disabled {
diff --git a/composables/cosmetics.js b/composables/cosmetics.js
index 619197cf..87637c7c 100644
--- a/composables/cosmetics.js
+++ b/composables/cosmetics.js
@@ -17,6 +17,7 @@ export const useCosmetics = () =>
developerMode: false,
notUsingBlockers: false,
hideModrinthAppPromos: false,
+ preferredDarkTheme: 'dark',
searchDisplayMode: {
mod: 'list',
plugin: 'list',
diff --git a/composables/theme.js b/composables/theme.js
index dfc6acc0..eac074cb 100644
--- a/composables/theme.js
+++ b/composables/theme.js
@@ -24,6 +24,7 @@ export const useTheme = () =>
export const updateTheme = (value, updatePreference = false) => {
const theme = useTheme()
+ const cosmetics = useCosmetics()
const themeCookie = useCookie('color-mode', {
maxAge: 60 * 60 * 24 * 365 * 10,
@@ -40,7 +41,7 @@ export const updateTheme = (value, updatePreference = false) => {
if (colorSchemeQueryList.matches) {
theme.value.value = 'light'
} else {
- theme.value.value = 'dark'
+ theme.value.value = cosmetics.value.preferredDarkTheme
}
} else {
theme.value.value = value
@@ -53,3 +54,5 @@ export const updateTheme = (value, updatePreference = false) => {
themeCookie.value = theme.value
}
+
+export const DARK_THEMES = ['dark', 'oled', 'retro']
diff --git a/layouts/default.vue b/layouts/default.vue
index 4f0df3a8..21774cc5 100644
--- a/layouts/default.vue
+++ b/layouts/default.vue
@@ -433,6 +433,7 @@ import ModalCreation from '~/components/ui/ModalCreation.vue'
import Avatar from '~/components/ui/Avatar.vue'
import { getProjectTypeMessage } from '~/utils/i18n-project-type.ts'
import { commonMessages } from '~/utils/common-messages.ts'
+import { DARK_THEMES } from '~/composables/theme.js'
const { formatMessage } = useVIntl()
@@ -724,7 +725,10 @@ function toggleBrowseMenu() {
}
}
function changeTheme() {
- updateTheme(app.$colorMode.value === 'dark' ? 'light' : 'dark', true)
+ updateTheme(
+ DARK_THEMES.includes(app.$colorMode.value) ? 'light' : cosmetics.value.preferredDarkTheme,
+ true
+ )
}
function hideStagingBanner() {
@@ -781,6 +785,15 @@ function hideStagingBanner() {
a {
align-items: center;
display: flex;
+
+ &:not(:focus-visible) {
+ outline: none;
+
+ &.router-link-exact-active {
+ outline: 2px solid transparent;
+ border-radius: 0.25rem;
+ }
+ }
}
.small-logo {
@@ -909,6 +922,7 @@ function hideStagingBanner() {
display: flex;
justify-content: center;
padding: 0;
+ outline: none;
.user-icon {
height: 2rem;
@@ -959,6 +973,7 @@ function hideStagingBanner() {
display: flex;
padding: 0.5rem 0.75rem;
width: 100%;
+ outline: none;
.icon {
margin-right: 0.5rem;
@@ -969,6 +984,7 @@ function hideStagingBanner() {
&.router-link-exact-active {
color: var(--color-button-text-active);
background-color: var(--color-button-bg);
+ outline: 2px solid transparent;
&.primary-color {
color: var(--color-button-text-active);
diff --git a/locales/en-US/index.json b/locales/en-US/index.json
index 24590752..0f2a8fba 100644
--- a/locales/en-US/index.json
+++ b/locales/en-US/index.json
@@ -191,6 +191,9 @@
"button.sign-out": {
"message": "Sign out"
},
+ "button.upload-image": {
+ "message": "Upload image"
+ },
"collection.button.delete-icon": {
"message": "Delete icon"
},
@@ -285,7 +288,10 @@
"message": "Grid view"
},
"input.view.list": {
- "message": "List view"
+ "message": "Rows view"
+ },
+ "label.changes-saved": {
+ "message": "Changes saved"
},
"label.collections": {
"message": "Collections"
@@ -779,6 +785,114 @@
"scopes.versionWrite.label": {
"message": "Write versions"
},
+ "settings.account.title": {
+ "message": "Account and security"
+ },
+ "settings.appearance.title": {
+ "message": "Appearance"
+ },
+ "settings.applications.title": {
+ "message": "Your applications"
+ },
+ "settings.authorized-apps.title": {
+ "message": "Authorized apps"
+ },
+ "settings.display.banner.developer-mode.button": {
+ "message": "Deactivate developer mode"
+ },
+ "settings.display.banner.developer-mode.description": {
+ "message": "
Developer mode is active. This will allow you to view the internal IDs of various things throughout Modrinth that may be helpful if you're a developer using the Modrinth API. Click on the Modrinth logo at the bottom of the page 5 times to toggle developer mode."
+ },
+ "settings.display.flags.description": {
+ "message": "Enable or disable certain features on this device."
+ },
+ "settings.display.flags.title": {
+ "message": "Feature flags"
+ },
+ "settings.display.project-list-layouts.datapack": {
+ "message": "Data Packs page"
+ },
+ "settings.display.project-list-layouts.description": {
+ "message": "Select your preferred layout for each page that displays project lists on this device."
+ },
+ "settings.display.project-list-layouts.mod": {
+ "message": "Mods page"
+ },
+ "settings.display.project-list-layouts.modpack": {
+ "message": "Modpacks page"
+ },
+ "settings.display.project-list-layouts.plugin": {
+ "message": "Plugins page"
+ },
+ "settings.display.project-list-layouts.resourcepack": {
+ "message": "Resource Packs page"
+ },
+ "settings.display.project-list-layouts.shader": {
+ "message": "Shaders page"
+ },
+ "settings.display.project-list-layouts.title": {
+ "message": "Project list layouts"
+ },
+ "settings.display.project-list-layouts.user": {
+ "message": "User profile pages"
+ },
+ "settings.display.sidebar.advanced-rendering.description": {
+ "message": "Enables advanced rendering such as blur effects that may cause performance issues without hardware-accelerated rendering."
+ },
+ "settings.display.sidebar.advanced-rendering.title": {
+ "message": "Advanced rendering"
+ },
+ "settings.display.sidebar.external-links-new-tab.description": {
+ "message": "Make links which go outside of Modrinth open in a new tab. No matter this setting, links on the same domain and in Markdown descriptions will open in the same tab, and links on ads and edit pages will open in a new tab."
+ },
+ "settings.display.sidebar.external-links-new-tab.title": {
+ "message": "Open external links in new tab"
+ },
+ "settings.display.sidebar.hide-app-promos.description": {
+ "message": "Hides the \"Get Modrinth App\" buttons from primary navigation. The Modrinth App page can still be found on the landing page or in the footer."
+ },
+ "settings.display.sidebar.hide-app-promos.title": {
+ "message": "Hide Modrinth App promotions"
+ },
+ "settings.display.sidebar.right-aligned-project-sidebar.description": {
+ "message": "Aligns the project details sidebar to the right of the page's content."
+ },
+ "settings.display.sidebar.right-aligned-project-sidebar.title": {
+ "message": "Right-aligned project sidebar"
+ },
+ "settings.display.sidebar.right-aligned-search-sidebar.description": {
+ "message": "Aligns the search filters sidebar to the right of the search results."
+ },
+ "settings.display.sidebar.right-aligned-search-sidebar.title": {
+ "message": "Right-aligned search sidebar"
+ },
+ "settings.display.theme.dark": {
+ "message": "Dark"
+ },
+ "settings.display.theme.description": {
+ "message": "Select your preferred color theme for Modrinth on this device."
+ },
+ "settings.display.theme.light": {
+ "message": "Light"
+ },
+ "settings.display.theme.oled": {
+ "message": "OLED"
+ },
+ "settings.display.theme.preferred-dark-theme": {
+ "message": "Preferred dark theme"
+ },
+ "settings.display.theme.preferred-light-theme": {
+ "message": "Preferred light theme"
+ },
+ "settings.display.theme.retro": {
+ "message": "Retro"
+ },
+ "settings.display.theme.system": {
+ "message": "Sync with system"
+ },
+ "settings.display.theme.title": {
+ "message": "Color theme"
+ },
"settings.language.categories.auto": {
"message": "Automatic"
},
@@ -858,10 +972,7 @@
"message": "Edit personal access token"
},
"settings.pats.title": {
- "message": "PATs"
- },
- "settings.pats.title.long": {
- "message": "Personal Access Tokens"
+ "message": "Personal access tokens"
},
"settings.pats.token.action.edit": {
"message": "Edit token"
@@ -881,6 +992,36 @@
"settings.pats.token.never-used": {
"message": "Never used"
},
+ "settings.profile.bio.description": {
+ "message": "A short description to tell everyone a little bit about you."
+ },
+ "settings.profile.bio.title": {
+ "message": "Bio"
+ },
+ "settings.profile.description": {
+ "message": "Your profile information is publicly viewable on Modrinth and through the
Modrinth API."
+ },
+ "settings.profile.profile-info": {
+ "message": "Profile information"
+ },
+ "settings.profile.profile-picture.reset": {
+ "message": "Reset"
+ },
+ "settings.profile.profile-picture.title": {
+ "message": "Profile picture"
+ },
+ "settings.profile.title": {
+ "message": "Public profile"
+ },
+ "settings.profile.username.description": {
+ "message": "A unique case-insensitive name to identify your profile."
+ },
+ "settings.profile.username.title": {
+ "message": "Username"
+ },
+ "settings.profile.visit-profile": {
+ "message": "Visit your profile"
+ },
"settings.sessions.action.revoke-session": {
"message": "Revoke session"
},
diff --git a/pages/dashboard/projects.vue b/pages/dashboard/projects.vue
index 477f2885..9af12597 100644
--- a/pages/dashboard/projects.vue
+++ b/pages/dashboard/projects.vue
@@ -502,6 +502,7 @@ export default defineNuxtComponent({
border-radius: var(--size-rounded-sm);
overflow: hidden;
margin-top: var(--spacing-card-md);
+ outline: 1px solid transparent;
.grid-table__row {
display: contents;
diff --git a/pages/settings.vue b/pages/settings.vue
index 5024b141..f7e671b6 100644
--- a/pages/settings.vue
+++ b/pages/settings.vue
@@ -1,65 +1,116 @@
-
-