diff --git a/docs/src/content/docs/contributing/maintainers/reviewing.md b/docs/src/content/docs/contributing/maintainers/reviewing.md
new file mode 100644
index 0000000000..c0c9074ced
--- /dev/null
+++ b/docs/src/content/docs/contributing/maintainers/reviewing.md
@@ -0,0 +1,28 @@
+---
+title: Reviewing userstyles
+description: Notes for performing thorough new userstyle reviews.
+sidebar:
+ order: 1
+---
+
+A key tenant of the userstyles philosophy is starting with a complete version. Generally, we avoid merging new userstyles that do not fully theme the target application. Note that fully theming a set of (core) pages of the application, while _fully_ not theming other pages of the application, is acceptable. The reasoning for this strict policy is that userstyles maintainers come and go and it is difficult to depend on continued maintenance down the road. If a userstyle isn't initially complete, it is unlikely that more of it will be completed later.
+
+## Inspecting the user interface
+
+- Inspect login/signup pages. A small part of websites, but nice to theme nonetheless.
+- Be careful around hover and active states of buttons and button-like components.
+- Internal links referenced in the website's footer are often good test pages if you aren't familiar with the site.
+- Settings and profile pages are often missed details.
+- Small details with input elements to note are borders, hover/active/focus borders, placeholder text and icons (the former should be themed by the standard library's defaults, but not always).
+
+## Reviewing source code
+
+Even if a userstyle is well-_themed_, it's also important that it is maintainable and _written_ well. For example, if a userstyle themes a website by targeting individual element selectors, but the website primarily makes use of a set of CSS variables under the hood, it's generally worth switching to the latter.
+
+- Organization is key. Some strategies include organization by page or component type (e.g. buttons and inputs in one section, cards and surfaces in another, etc.). Consistent comment annotations is best.
+- Importantly, ensure palette colors are used aptly and appropriately.
+- Avoid overcorrections of palette colors, such as `darken` or `lighten` operations in large amounts (`darken` by 30% produces a color that is relatively distinct from the pastel palette).
+
+## Writing feedback and review comments
+
+When leaving a comment pointing out issues discovered in a review, it is important to make suggestions digestible and actionable. Provide links to relevant pages, descriptions of steps to access a certain component, and screenshots.
diff --git a/lib/mediawiki/v1.less b/lib/mediawiki/v1.less
new file mode 100644
index 0000000000..51dac9c797
--- /dev/null
+++ b/lib/mediawiki/v1.less
@@ -0,0 +1,534 @@
+#__mediawiki {
+ .reset() {
+ html.skin-theme-clientpref-night, html.skin-theme-clientpref-os {
+ /* Logo */
+ .skin-invert-image img,
+ .skin-invert,
+ .oo-ui-iconElement-icon:not(
+ .oo-ui-image-progressive,
+ .oo-ui-image-destructive,
+ .oo-ui-checkboxInputWidget-checkIcon,
+ .oo-ui-image-invert,
+ .mw-no-invert
+ ),
+ .oo-ui-indicatorElement-indicator {
+ filter: none;
+ }
+ }
+
+ /* MediaWiki only applies the following overrides on dark and automatic appearance settings, leaving broken elements with the light appearance. */
+
+ .hatnote:not(.notheme),
+ .dablink:not(.notheme),
+ .rellink:not(.notheme),
+ .infobox:not(.notheme) {
+ color: var(--color-base) !important;
+ background-color: var(--background-color-interactive-subtle) !important;
+ }
+ .infobox td:not(.notheme),
+ .infobox th:not(.notheme),
+ .infobox-above:not(.notheme),
+ .infobox p:not(.notheme),
+ .infobox > div:not(.notheme),
+ .infobox caption:not(.notheme),
+ .infobox--frwiki td:not(.notheme),
+ .infobox--frwiki th:not(.notheme),
+ .infobox--frwiki p:not(.notheme),
+ .infobox--frwiki > div:not(.notheme),
+ .infobox--frwiki caption:not(.notheme),
+ .sinottico th:not(.notheme),
+ .infobox-header:not(.notheme),
+ .skin-nightmode-reset-color:not(.notheme),
+ .navigation-box:not(.notheme),
+ .metadata:not(.notheme),
+ .quotebox:not(.notheme),
+ .side-box:not(.notheme),
+ .side-box div:not(.notheme),
+ .navbox:not(.notheme),
+ .navbox-subgroup:not(.notheme),
+ .navbox-group:not(.notheme),
+ .navbox-even:not(.notheme),
+ .navbox-abovebelow:not(.notheme),
+ .navbox-title:not(.notheme) {
+ background: inherit !important;
+ color: inherit !important;
+ border-color: var(--border-color-subtle) !important;
+ }
+ .ns-100 .mw-parser-output :not(.notheme, a) {
+ background: inherit !important;
+ color: inherit !important;
+ border-color: var(--border-color-subtle) !important;
+ }
+ }
+
+ .base() {
+ --color-base: @text;
+ --color-base--hover: @text;
+ --color-emphasized: @text;
+ --color-subtle: @subtext0;
+
+ --color-inverted: @base;
+ // Locked to base as it is often used on accent,
+ // The same as color-inverted in light mode, but does not change in dark mode. Use this for things that should be white in both modes.
+ --color-inverted-fixed: @base;
+
+ --color-progressive: @accent;
+ --color-progressive--hover: lighten(@accent, 5%);
+ --color-progressive--active: darken(@accent, 10%);
+ --color-progressive--focus: darken(@accent, 10%);
+
+ --color-destructive: @red;
+ --color-destructive--hover: @maroon;
+ --color-destructive--active: darken(@red, 4%);
+ --color-destructive--visited: spin(@red, 15);
+
+ --color-visited: @lavender;
+
+ --color-error: @red;
+ --color-warning: @yellow;
+ --color-success: @green;
+ --color-notice: @text;
+
+ --color-icon-error: @red;
+ --color-icon-warning: @yellow;
+ --color-icon-success: @green;
+ --color-icon-notice: @subtext0;
+
+ --color-content-added: @green;
+ --color-content-removed: @maroon;
+ --color-placeholder: @subtext0;
+ --color-disabled: @overlay2;
+
+ --box-shadow-color-base: @overlay0;
+ --box-shadow-color-progressive--active: fade(@accent, 10%);
+ --box-shadow-color-progressive--focus: fade(@accent, 50%);
+ --box-shadow-color-progressive-selected: fade(@accent, 60%);
+ --box-shadow-color-progressive-selected--hover: fade(@accent, 70%);
+ --box-shadow-color-progressive-selected--active: fade(@accent, 80%);
+ --box-shadow-color-destructive--focus: fade(@accent, 50%);
+ --box-shadow-color-inverted: @base;
+ --box-shadow-color-alpha-base: rgba(0, 0, 0, 0.87);
+ --box-shadow-color-transparent: transparent;
+
+ // Background colors for static elements (for page layout, sections, etc.) from here on.
+ --background-color-base: @base;
+ // The same as background-color-base in light mode, but does not change in dark mode. Use this for things that should be white in both modes.
+ --background-color-base-fixed: @catppuccin[@latte][@base];
+ --background-color-neutral: @surface0;
+ --background-color-neutral-subtle: mix(@surface0, @base, 50%);
+ --background-color-interactive: @surface0;
+ --background-color-interactive--hover: @surface1;
+ --background-color-interactive--active: @surface2;
+ --background-color-interactive-subtle: mix(@surface0, @base, 50%);
+ --background-color-interactive-subtle--hover: @surface0;
+ --background-color-interactive-subtle--active: @surface1;
+ --background-color-disabled: @surface1;
+ --background-color-disabled-subtle: @surface0;
+ --background-color-inverted: @text;
+
+ --background-color-progressive: @accent;
+ --background-color-progressive--hover: darken(@accent, 5%);
+ --background-color-progressive--active: darken(@accent, 10%);
+ --background-color-progressive--focus: lighten(@accent, 10%);
+ --background-color-progressive-subtle: mix(@accent, @base, 30%);
+ --background-color-progressive-subtle--hover: lighten(
+ mix(@accent, @base, 30%),
+ 5%
+ );
+ --background-color-progressive-subtle--active: lighten(
+ mix(@accent, @base, 30%),
+ 10%
+ );
+
+ --background-color-destructive: @red;
+ --background-color-destructive--hover: darken(@red, 5%);
+ --background-color-destructive--active: darken(@red, 10%);
+ --background-color-destructive--focus: lighten(@accent, 10%);
+ --background-color-destructive-subtle: mix(@red, @base, 30%);
+ --background-color-destructive-subtle--hover: lighten(
+ mix(@red, @base, 30%),
+ 5%
+ );
+ --background-color-destructive-subtle--active: lighten(
+ mix(@red, @base, 30%),
+ 10%
+ );
+
+ --background-color-error: @red;
+ --background-color-error--hover: darken(@red, 5%);
+ --background-color-error--active: darken(@red, 10%);
+ --background-color-error-subtle: mix(@red, @base, 30%);
+ --background-color-error-subtle--hover: lighten(mix(@red, @base, 30%), 5%);
+ --background-color-error-subtle--active: lighten(
+ mix(@red, @base, 30%),
+ 10%
+ );
+
+ --background-color-warning-subtle: mix(@yellow, @base, 30%);
+ --background-color-success-subtle: mix(@green, @base, 30%);
+ --background-color-notice-subtle: mix(@text, @base, 30%);
+
+ --background-color-content-added: mix(@blue, @base, 30%);
+ --background-color-content-removed: mix(@yellow, @base, 30%);
+
+ --background-color-backdrop-light: fade(@base, 65%);
+ --background-color-backdrop-dark: fade(@text, 65%);
+
+ --background-color-button-quiet--hover: fade(@accent, 2.7%);
+ --background-color-button-quiet--active: fade(@accent, 8.2%);
+
+ --background-color-input-binary--checked: @accent;
+
+ --background-color-tab-list-item-framed--hover: fade(
+ @catppuccin[@latte][@base],
+ 30%
+ );
+ --background-color-tab-list-item-framed--active: fade(
+ @catppuccin[@latte][@base],
+ 65%
+ );
+
+ --border-base: 1px solid var(--border-color-base);
+ --border-subtle: 1px solid var(--border-color-subtle);
+
+ --border-color-base: @overlay0;
+ --border-color-emphasized: @text;
+ --border-color-subtle: @surface2;
+ --border-color-muted: @surface1;
+ --border-color-interactive: @overlay0;
+ --border-color-interactive--hover: @overlay1;
+ --border-color-interactive--active: @overlay2;
+ --border-color-disabled: @overlay0;
+ --border-color-inverted: @base;
+ // The same as color-inverted in light mode, but does not change in dark mode. Use this for things that should be white in both modes.
+ --border-color-inverted-fixed: @catppuccin[@latte][@base];
+
+ --border-color-progressive: var(--color-progressive);
+ --border-color-progressive--hover: var(--color-progressive--hover);
+ --border-color-progressive--active: var(--color-progressive--active);
+ --border-color-progressive--focus: var(--color-progressive--focus);
+ --border-color-destructive: var(--color-destructive);
+ --border-color-destructive--hover: var(--color-destructive--hover);
+ --border-color-destructive--active: var(--color-destructive--active);
+ --border-color-destructive--focus: var(--color-destructive--focus);
+ --border-color-error: var(--color-error);
+ --border-color-error--hover: var(--color-error--hover);
+ --border-color-error--active: var(--color-error--active);
+ --border-color-warning: var(--color-warning);
+ --border-color-warning--hover: var(--color-warning--hover);
+ --border-color-warning--active: var(--color-warning--active);
+ --border-color-success: var(--color-success);
+ --border-color-notice: var(--color-notice);
+ --border-color-content-added: var(--color-content-added);
+ --border-color-content-removed: var(--color-content-removed);
+
+ --border-color-divider: @overlay1;
+ --border-color-input--hover: var(--border-color-interactive);
+ --border-color-input-binary: var(--border-color-interactive);
+ --border-color-input-binary--hover: var(--border-color-progressive--hover);
+ --border-color-input-binary--active: var(
+ --border-color-progressive--active
+ );
+ --border-color-input-binary--focus: var(--border-color-progressive--focus);
+ --border-color-input-binary--checked: var(--border-color-progressive);
+
+ /* Other */
+
+ .infobox {
+ border-color: @surface2;
+ }
+
+ [style="color:white;"], [style*="color:#ffffff"] {
+ color: @text !important;
+ }
+ .mw-collapsible-toggle-default {
+ &::before, &::after {
+ color: @subtext0;
+ }
+ }
+
+ a {
+ &:focus {
+ outline-color: @accent;
+ }
+ }
+
+ .mw-parser-output {
+ a.external {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}");
+ }
+ }
+
+ /* Math equations */
+ .mwe-math-element img.skin-invert {
+ filter: @text-filter;
+ }
+
+ /* OOUI */
+ .oo-ui-indicator-down {
+ filter: @text-filter !important;
+ }
+ .oo-ui-image-invert.oo-ui-icon-check, .mw-ui-icon-check-invert::before {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}");
+ }
+
+ .oo-ui-radioInputWidget [type="radio"] + span {
+ background-color: @text;
+ }
+
+ .cdx-radio__input:enabled:checked + .cdx-radio__icon,
+ .oo-ui-radioInputWidget [type="radio"]:checked + span {
+ background-color: @base;
+ }
+
+ /* View source */
+ textarea {
+ background-color: @mantle;
+ color: @text;
+ }
+
+ /* View history */
+ .flaggedrevs-color-1 {
+ background-color: fade(@blue, 10%);
+ }
+
+ /* Language search and settings */
+ .uls-search,
+ .uls-filtersuggestion,
+ .uls-lcd,
+ #uls-settings-block.uls-settings-block--vector-2022.uls-settings-block--with-add-languages,
+ #language-settings-dialog,
+ #languagesettings-settings-panel,
+ .uls-language-settings-close-block {
+ background-color: @mantle;
+ }
+ .uls-language-block a {
+ color: @accent;
+ }
+ .uls-search-label,
+ #uls-settings-block.uls-settings-block--vector-2022
+ > button.uls-add-languages-button,
+ #uls-settings-block.uls-settings-block--vector-2022
+ > button.uls-language-settings-button,
+ #uls-settings-block.uls-settings-block--vector-2022
+ > button.uls-language-settings-button:hover,
+ .uls-icon-close,
+ .uls-languagefilter-clear {
+ filter: @text-filter;
+ }
+ .uls-sub-panel, .language-settings-buttons {
+ border-color: @surface1;
+ }
+ .uls-language-block > ul > li:hover {
+ background-color: @surface0;
+ }
+ .languagesettings-menu .menu-section.active {
+ background-color: @surface1;
+ }
+
+ /* Page preview */
+ .mwe-popups .mwe-popups-extract[dir="ltr"]::after {
+ background-image: linear-gradient(to right, transparent, @base 50%);
+ }
+ .mwe-popups .mwe-popups-extract[dir="rtl"]::after {
+ background-image: linear-gradient(to left, transparent, @base 50%);
+ }
+
+ /* Icons */
+
+ .vector-page-titlebar
+ .mw-portlet-lang
+ .vector-dropdown-label.cdx-button--action-progressive.cdx-button--weight-quiet::after {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}") !important;
+ filter: none;
+ }
+
+ .vector-icon {
+ filter: none !important;
+
+ &.mw-ui-icon-wikimedia-menu {
+ @svg: escape(
+ ''
+ );
+ content: url("data:image/svg+xml,@{svg}");
+ }
+
+ &.mw-ui-icon-wikimedia-ellipsis {
+ @svg: escape(
+ ''
+ );
+ content: url("data:image/svg+xml,@{svg}");
+ }
+
+ &.mw-ui-icon-wikimedia-appearance {
+ @svg: escape(
+ ''
+ );
+ content: url("data:image/svg+xml,@{svg}");
+ }
+
+ &.mw-ui-icon-wikimedia-expand {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}") !important;
+ }
+
+ &.mw-ui-icon-wikimedia-language-progressive {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}") !important;
+ }
+
+ &.mw-ui-icon-wikimedia-language {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}") !important;
+ }
+
+ &.mw-ui-icon-wikimedia-listBullet {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}") !important;
+ }
+
+ &.mw-ui-icon-wikimedia-fullScreen {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}");
+ }
+
+ &.mw-ui-icon-wikimedia-userAvatar {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}");
+ }
+
+ &.mw-ui-icon-wikimedia-watchlist {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}");
+ }
+
+ &.mw-ui-icon-wikimedia-tray {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}");
+ }
+
+ &.mw-ui-icon-wikimedia-bell {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}");
+ }
+
+ &.mw-ui-icon-wikimedia-star {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}");
+ }
+
+ &.mw-ui-icon-wikimedia-userTalk {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}");
+ }
+
+ &.mw-ui-icon-wikimedia-sandbox {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}");
+ }
+
+ &.mw-ui-icon-wikimedia-settings {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}");
+ }
+
+ &.mw-ui-icon-wikimedia-unStar {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}");
+ }
+
+ &.mw-ui-icon-wikimedia-labFlask {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}");
+ }
+
+ &.mw-ui-icon-wikimedia-userContributions {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}");
+ }
+
+ &.mw-ui-icon-wikimedia-logOut {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}");
+ }
+
+ &.mw-ui-icon-wikimedia-search {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}");
+ }
+
+ &.mw-ui-icon-wikimedia-edit {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}");
+ }
+
+ &.mw-ui-icon-wikimedia-history {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}");
+ }
+
+ &.mw-ui-icon-wikimedia-speechBubbles {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}");
+ }
+
+ &.mw-ui-icon-wikimedia-editLock {
+ @svg: escape(
+ ''
+ );
+ background-image: url("data:image/svg+xml,@{svg}");
+ }
+ }
+ }
+}