- {buttons}
-
-
{!suspended && children}
{showDimmer &&
}
- {fancy && (
+ {fancy && scrollbars && (
<>
{
)}
);
-};
+}
type ContentProps = Partial<{
className: string;
@@ -158,20 +173,46 @@ type ContentProps = Partial<{
ComponentProps
&
PropsWithChildren;
-const WindowContent = (props: ContentProps) => {
+function WindowContent(props: ContentProps) {
const { className, fitted, children, ...rest } = props;
+ const [altDown, setAltDown] = useState(false);
+
+ const dragStartIfAltHeld = (event) => {
+ if (altDown) {
+ dragStartHandler(event);
+ }
+ };
+ Byond.subscribeTo('resetposition', (payload) => {
+ setWindowPosition([0, 0]);
+ storeWindowGeometry();
+ });
return (
+ {
+ if (KEY_ALT === e.code) {
+ setAltDown(true);
+ logger.log(`alt on ${altDown}`);
+ }
+ }}
+ onKeyUp={(e: KeyEvent) => {
+ if (KEY_ALT === e.code) {
+ setAltDown(false);
+ logger.log(`alt off ${altDown}`);
+ }
+ }}
+ />
+
{(fitted && children) || (
{children}
)}
);
-};
+}
Window.Content = WindowContent;
diff --git a/tgui/packages/tgui/package.json b/tgui/packages/tgui/package.json
index eaef4e67590..475131d94cd 100644
--- a/tgui/packages/tgui/package.json
+++ b/tgui/packages/tgui/package.json
@@ -4,17 +4,18 @@
"dependencies": {
"common": "workspace:*",
"dateformat": "^5.0.3",
- "dompurify": "^3.2.5",
- "es-toolkit": "^1.39.3",
+ "dompurify": "^3.3.1",
+ "es-toolkit": "^1.44.0",
"highlight.js": "^11.11.1",
- "jotai": "^2.12.4",
- "js-yaml": "^4.1.0",
- "marked": "^15.0.11",
- "marked-base-url": "^1.1.6",
- "marked-smartypants": "^1.1.9",
- "react": "^19.1.0",
- "react-dom": "^19.1.0",
- "tgui-core": "^4.2.3",
+ "jotai": "^2.16.2",
+ "js-yaml": "^4.1.1",
+ "marked": "^17.0.1",
+ "marked-base-url": "^1.1.8",
+ "marked-smartypants": "^1.1.11",
+ "react": "^19.2.3",
+ "react-dom": "^19.2.3",
+ "react-json-tree": "^0.20.0",
+ "tgui-core": "^5.9.0",
"tgui-dev-server": "workspace:*"
},
"private": true
diff --git a/tgui/packages/tgui/renderer.ts b/tgui/packages/tgui/renderer.ts
index 90cd182e56a..ed899cfddfb 100644
--- a/tgui/packages/tgui/renderer.ts
+++ b/tgui/packages/tgui/renderer.ts
@@ -1,7 +1,6 @@
import { perf } from 'common/perf';
import type { ReactNode } from 'react';
import { createRoot, type Root } from 'react-dom/client';
-
import { createLogger } from './logging';
const logger = createLogger('renderer');
diff --git a/tgui/packages/tgui/routes.tsx b/tgui/packages/tgui/routes.tsx
index f358f52593a..81f3f3eaa45 100644
--- a/tgui/packages/tgui/routes.tsx
+++ b/tgui/packages/tgui/routes.tsx
@@ -4,32 +4,40 @@
* @license MIT
*/
-import { useBackend } from './backend';
-import { useDebug } from './debug';
+import { useAtomValue } from 'jotai';
+import { KitchenSink } from './debug/KitchenSink';
+import { backendStateAtom } from './events/store';
import { LoadingScreen } from './interfaces/common/LoadingScreen';
import { Window } from './layouts';
const requireInterface = require.context('./interfaces');
-const routingError =
- (type: 'notFound' | 'missingExport', name: string) => () => {
- return (
-
-
- {type === 'notFound' && (
-
- Interface {name} was not found.
-
- )}
- {type === 'missingExport' && (
-
- Interface {name} is missing an export.
-
- )}
-
-
- );
- };
+type RoutingErrorProps = {
+ type: 'notFound' | 'missingExport' | 'unknown';
+ name: string;
+};
+
+export function RoutingErrorWindow(props: RoutingErrorProps) {
+ const { type, name } = props;
+
+ return (
+
+
+ {type === 'notFound' && (
+
+ Interface {name} was not found.
+
+ )}
+ {type === 'missingExport' && (
+
+ Interface {name} is missing an export.
+
+ )}
+ {type === 'unknown' && An unknown error has occurred.
}
+
+
+ );
+}
// Displays an empty Window with scrollable content
function SuspendedWindow() {
@@ -52,25 +60,7 @@ function RefreshingWindow() {
}
// Get the component for the current route
-export function getRoutedComponent() {
- const { suspended, config } = useBackend();
- const { kitchenSink = false } = useDebug();
-
- if (suspended) {
- return SuspendedWindow;
- }
- if (config?.refreshing) {
- return RefreshingWindow;
- }
-
- if (process.env.NODE_ENV !== 'production') {
- // Show a kitchen sink
- if (kitchenSink) {
- return require('./debug').KitchenSink;
- }
- }
-
- const name = config?.interface?.name;
+export function getRoutedComponent(name: string) {
const interfacePathBuilders = [
(name: string) => `./${name}.tsx`,
(name: string) => `./${name}.jsx`,
@@ -88,19 +78,56 @@ export function getRoutedComponent() {
esModule = requireInterface(interfacePath);
} catch (err) {
if (err.code !== 'MODULE_NOT_FOUND') {
- throw err;
+ throw new Error('notFound');
}
}
}
if (!esModule) {
- return routingError('notFound', name);
+ throw new Error('notFound');
}
const Component = esModule[name];
if (!Component) {
- return routingError('missingExport', name);
+ throw new Error('missingExport');
}
return Component;
}
+
+export function RoutedComponent() {
+ const { suspended, config, debug } = useAtomValue(backendStateAtom);
+
+ if (suspended) {
+ return ;
+ }
+ if (config.refreshing) {
+ return ;
+ }
+
+ if (process.env.NODE_ENV !== 'production') {
+ if (debug.kitchenSink) {
+ return ;
+ }
+ }
+
+ const name = config?.interface?.name;
+ if (!name) {
+ return ;
+ }
+
+ try {
+ const Component = getRoutedComponent(name);
+
+ return ;
+ } catch (err) {
+ switch (err.message) {
+ case 'notFound':
+ return ;
+ case 'missingExport':
+ return ;
+ default:
+ return ;
+ }
+ }
+}
diff --git a/tgui/packages/tgui/stack.ts b/tgui/packages/tgui/stack.ts
new file mode 100644
index 00000000000..03967c38f2b
--- /dev/null
+++ b/tgui/packages/tgui/stack.ts
@@ -0,0 +1,28 @@
+import { configAtom, store } from './events/store';
+import { logger } from './logging';
+
+/**
+ * Creates a function, which can be assigned to window.__augmentStack__
+ * to augment reported stack traces with useful data for debugging.
+ */
+export const createStackAugmentor =
+ () =>
+ (stack: string, error?: Error): string => {
+ error = error || new Error(stack.split('\n')[0]);
+ error.stack = error.stack || stack;
+
+ logger.log('FatalError:', error);
+ const config = store.get(configAtom);
+
+ return (
+ stack +
+ '\nUser Agent: ' +
+ navigator.userAgent +
+ '\nState: ' +
+ JSON.stringify({
+ ckey: config?.client?.ckey,
+ interface: config?.interface,
+ window: config?.window,
+ })
+ );
+ };
diff --git a/tgui/packages/tgui/store.ts b/tgui/packages/tgui/store.ts
deleted file mode 100644
index cb20bbf8a53..00000000000
--- a/tgui/packages/tgui/store.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-/**
- * @file
- * @copyright 2020 Aleksej Komarov
- * @license MIT
- */
-
-import {
- applyMiddleware,
- combineReducers,
- createStore,
- type Middleware,
- type Reducer,
- type Store,
-} from 'common/redux';
-import { flow } from 'tgui-core/fp';
-
-import { assetMiddleware } from './assets';
-import { backendMiddleware, backendReducer } from './backend';
-import { debugMiddleware, debugReducer, relayMiddleware } from './debug';
-import { createLogger } from './logging';
-
-type ConfigureStoreOptions = {
- sideEffects?: boolean;
- reducer?: Reducer;
- middleware?: {
- pre?: Middleware[];
- post?: Middleware[];
- };
-};
-
-type StackAugmentor = (stack: string, error?: Error) => string;
-
-type StoreProviderProps = {
- store: Store;
- children: any;
-};
-
-const logger = createLogger('store');
-
-export const configureStore = (options: ConfigureStoreOptions = {}): Store => {
- const { sideEffects = true, reducer, middleware } = options;
- const rootReducer: Reducer = flow([
- combineReducers({
- debug: debugReducer,
- backend: backendReducer,
- }),
- reducer as any,
- ]);
-
- const middlewares: Middleware[] = !sideEffects
- ? []
- : [
- ...(middleware?.pre || []),
- assetMiddleware,
- backendMiddleware,
- ...(middleware?.post || []),
- ];
-
- if (process.env.NODE_ENV !== 'production') {
- // We are using two if statements because Webpack is capable of
- // removing this specific block as dead code.
- if (sideEffects) {
- middlewares.unshift(loggingMiddleware, debugMiddleware, relayMiddleware);
- }
- }
-
- const enhancer = applyMiddleware(...middlewares);
- const store = createStore(rootReducer, enhancer);
-
- // Globals
- window.__store__ = store;
- window.__augmentStack__ = createStackAugmentor(store);
-
- return store;
-};
-
-const loggingMiddleware: Middleware = (store) => (next) => (action) => {
- const { type } = action;
- logger.debug(
- 'action',
- type === 'update' || type === 'backend/update' ? { type } : action,
- );
- return next(action);
-};
-
-/**
- * Creates a function, which can be assigned to window.__augmentStack__
- * to augment reported stack traces with useful data for debugging.
- */
-const createStackAugmentor =
- (store: Store): StackAugmentor =>
- (stack, error) => {
- error = error || new Error(stack.split('\n')[0]);
- error.stack = error.stack || stack;
-
- logger.log('FatalError:', error);
- const state = store.getState();
- const config = state?.backend?.config;
-
- return (
- stack +
- '\nUser Agent: ' +
- navigator.userAgent +
- '\nState: ' +
- JSON.stringify({
- ckey: config?.client?.ckey,
- interface: config?.interface,
- window: config?.window,
- })
- );
- };
diff --git a/tgui/packages/tgui/styles/atomic/candystripe.scss b/tgui/packages/tgui/styles/atomic/candystripe.scss
new file mode 100644
index 00000000000..d80edd85cdc
--- /dev/null
+++ b/tgui/packages/tgui/styles/atomic/candystripe.scss
@@ -0,0 +1,13 @@
+/**
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+/* Using :where for 0 specificity - https://developer.mozilla.org/en-US/docs/Web/CSS/:where */
+:where(.candystripe:nth-child(odd)) {
+ background-color: var(--candystripe-odd);
+}
+
+:where(.candystripe:nth-child(even)) {
+ background-color: var(--candystripe-even);
+}
diff --git a/tgui/packages/tgui/styles/atomic/centered-image.scss b/tgui/packages/tgui/styles/atomic/centered-image.scss
new file mode 100644
index 00000000000..cce5bfdf2c1
--- /dev/null
+++ b/tgui/packages/tgui/styles/atomic/centered-image.scss
@@ -0,0 +1,7 @@
+.centered-image {
+ position: absolute;
+ height: 100%;
+ left: 50%;
+ top: 50%;
+ transform: translateX(-50%) translateY(-50%) scale(0.8);
+}
diff --git a/tgui/packages/tgui/styles/atomic/color.scss b/tgui/packages/tgui/styles/atomic/color.scss
new file mode 100644
index 00000000000..8805e38272c
--- /dev/null
+++ b/tgui/packages/tgui/styles/atomic/color.scss
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+@use '../colors.scss';
+
+$map-keys: colors.$color-map !default;
+
+@each $color-name, $color-value in $map-keys {
+ .color-#{$color-name} {
+ color: hsl(from $color-value h s calc(l + var(--adjust-color))) !important;
+ }
+}
+
+@each $color-name, $color-value in $map-keys {
+ .color-bg-#{$color-name} {
+ background-color: hsl(
+ from $color-value h s calc(l - var(--adjust-color))
+ ) !important;
+ }
+}
diff --git a/tgui/packages/tgui/styles/atomic/debug-layout.scss b/tgui/packages/tgui/styles/atomic/debug-layout.scss
new file mode 100644
index 00000000000..0b0a2b7f4b2
--- /dev/null
+++ b/tgui/packages/tgui/styles/atomic/debug-layout.scss
@@ -0,0 +1,17 @@
+/**
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+.debug-layout,
+.debug-layout *:not(g):not(path) {
+ color: hsla(0, 0%, 100%, 0.9) !important;
+ background: transparent !important;
+ outline: 1px solid hsla(0, 0%, 100%, 0.5) !important;
+ box-shadow: none !important;
+ filter: none !important;
+
+ &:hover {
+ outline-color: hsla(0, 0%, 100%, 0.8) !important;
+ }
+}
diff --git a/tgui/packages/tgui/styles/atomic/fit-text.scss b/tgui/packages/tgui/styles/atomic/fit-text.scss
new file mode 100644
index 00000000000..1d49e13c8e3
--- /dev/null
+++ b/tgui/packages/tgui/styles/atomic/fit-text.scss
@@ -0,0 +1,14 @@
+$mqIterations: 19;
+@mixin fontResize($iterations) {
+ $i: 1;
+ @while $i <= $iterations {
+ @media all and (min-width: 100px * $i) {
+ .fit-text {
+ font-size: 0.1em * $i;
+ }
+ }
+ $i: $i + 1;
+ }
+}
+
+@include fontResize($mqIterations);
diff --git a/tgui/packages/tgui/styles/atomic/links.scss b/tgui/packages/tgui/styles/atomic/links.scss
new file mode 100644
index 00000000000..07d7fef898e
--- /dev/null
+++ b/tgui/packages/tgui/styles/atomic/links.scss
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2021 Celotajs (https://github.com/celotajstg)
+ * SPDX-License-Identifier: MIT
+ */
+
+a {
+ cursor: var(--cursor-pointer);
+ color: var(--color-hyperlink);
+ transition: color var(--transition-time-medium);
+ text-decoration: none;
+
+ &:hover,
+ &:active {
+ color: hsl(from var(--color-hyperlink) h s calc(l + var(--adjust-hover)));
+ }
+
+ &:visited {
+ color: var(--color-hyperlink-visited);
+
+ &:hover,
+ &:active {
+ color: hsl(
+ from var(--color-hyperlink-visited) h s calc(l + var(--adjust-hover))
+ );
+ }
+ }
+}
diff --git a/tgui/packages/tgui/styles/atomic/outline.scss b/tgui/packages/tgui/styles/atomic/outline.scss
new file mode 100644
index 00000000000..5fe40646f7f
--- /dev/null
+++ b/tgui/packages/tgui/styles/atomic/outline.scss
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+@use '../colors.scss';
+
+.outline-dotted {
+ outline-style: dotted !important;
+}
+
+.outline-dashed {
+ outline-style: dashed !important;
+}
+
+.outline-solid {
+ outline-style: solid !important;
+}
+
+.outline-double {
+ outline-style: double !important;
+}
+
+.outline-groove {
+ outline-style: groove !important;
+}
+
+.outline-ridge {
+ outline-style: ridge !important;
+}
+
+.outline-inset {
+ outline-style: inset !important;
+}
+
+.outline-outset {
+ outline-style: outset !important;
+}
+
+$map-keys: colors.$color-map !default;
+
+@each $color-name, $color-value in $map-keys {
+ /* prettier-ignore */
+ .outline-color-#{$color-name} {
+ outline: var(--border-thickness-small) solid hsl(from $color-value h s calc(l + var(--adjust-color))) !important;
+ }
+}
diff --git a/tgui/packages/tgui/styles/atomic/text.scss b/tgui/packages/tgui/styles/atomic/text.scss
new file mode 100644
index 00000000000..512e11eaca9
--- /dev/null
+++ b/tgui/packages/tgui/styles/atomic/text.scss
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+.text-left {
+ text-align: left;
+}
+
+.text-center {
+ text-align: center;
+}
+
+.text-right {
+ text-align: right;
+}
+
+.text-baseline {
+ text-align: baseline;
+}
+
+.text-justify {
+ text-align: justify;
+}
+
+.text-nowrap {
+ white-space: nowrap;
+}
+
+.text-pre {
+ white-space: pre;
+}
+
+.text-bold {
+ font-weight: bold;
+}
+
+.text-italic {
+ font-style: italic;
+}
+
+.text-underline {
+ text-decoration: underline;
+}
+
+.spoiler {
+ background-color: hsl(0, 0%, 50%);
+ color: transparent;
+}
+
+.spoiler:hover {
+ background-color: inherit;
+ color: inherit;
+}
diff --git a/tgui/packages/tgui/styles/interfaces/ColorMatrixEditor.scss b/tgui/packages/tgui/styles/interfaces/ColorMatrixEditor.scss
new file mode 100644
index 00000000000..99e05c5c5b2
--- /dev/null
+++ b/tgui/packages/tgui/styles/interfaces/ColorMatrixEditor.scss
@@ -0,0 +1,14 @@
+.MatrixEditor__Floating {
+ height: 290px;
+ width: 580px;
+ background-color: var(--color-section);
+ backdrop-filter: var(--blur-medium);
+ border: var(--border-thickness-tiny) solid var(--color-border);
+ border-radius: var(--border-radius-medium);
+ box-shadow: var(--shadow-glow-medium) hsla(0, 0%, 0%, 0.5);
+ margin: var(--space-m);
+ color: var(--color-text);
+ overflow-y: scroll;
+ overflow-x: hidden;
+ padding: var(--space-m);
+}
diff --git a/tgui/packages/tgui/styles/interfaces/ColorPicker.scss b/tgui/packages/tgui/styles/interfaces/ColorPicker.scss
new file mode 100644
index 00000000000..3b8be44557c
--- /dev/null
+++ b/tgui/packages/tgui/styles/interfaces/ColorPicker.scss
@@ -0,0 +1,150 @@
+/**
+ * MIT License
+ * https://github.com/omgovich/react-colorful/
+ *
+ * Copyright (c) 2020 Vlad Shilov
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+@use '../colors.scss';
+@use '../base.scss';
+
+.react-colorful {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ width: 200px;
+ height: 200px;
+ user-select: none;
+ cursor: default;
+}
+
+.react-colorful__saturation_value {
+ position: relative;
+ flex-grow: 1;
+ border-color: transparent; /* Fixes https://github.com/omgovich/react-colorful/issues/139 */
+ border-bottom: 12px solid hsl(0, 0%, 0%);
+ border-radius: 8px 8px 0 0;
+ background-image:
+ linear-gradient(to top, hsla(0, 0%, 0%, 255), hsla(0, 0%, 0%, 0)),
+ linear-gradient(to right, hsla(0, 0%, 100%, 255), hsla(0, 0%, 100%, 0));
+}
+
+.react-colorful__pointer-fill,
+.react-colorful__alpha-gradient {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ pointer-events: none;
+ border-radius: inherit;
+}
+
+/* Improve elements rendering on light backgrounds */
+.react-colorful__alpha-gradient,
+.react-colorful__saturation_value {
+ box-shadow: inset 0 0 0 1px hsla(0, 0%, 0%, 0.05);
+}
+
+.react-colorful__hue,
+.react-colorful__r,
+.react-colorful__g,
+.react-colorful__b,
+.react-colorful__alpha,
+.react-colorful__saturation,
+.react-colorful__value {
+ position: relative;
+ height: 24px;
+}
+
+.react-colorful__hue {
+ background: linear-gradient(
+ to right,
+ hsl(0, 100%, 50%) 0%,
+ hsl(60, 100%, 50%) 17%,
+ hsl(120, 100%, 50%) 33%,
+ hsl(180, 100%, 50%) 50%,
+ hsl(240, 100%, 50%) 67%,
+ hsl(300, 100%, 50%) 83%,
+ hsl(0, 100%, 50%) 100%
+ );
+}
+
+.react-colorful__r {
+ background: linear-gradient(to right, hsl(0, 0%, 0%), hsl(0, 100%, 50%));
+}
+
+.react-colorful__g {
+ background: linear-gradient(to right, hsl(0, 0%, 0%), hsl(120, 100%, 50%));
+}
+
+.react-colorful__b {
+ background: linear-gradient(to right, hsl(0, 0%, 0%), hsl(240, 100%, 50%));
+}
+
+/* Round bottom corners of the last element: `Hue` for `ColorPicker` or `Alpha` for `AlphaColorPicker` */
+.react-colorful__last-control {
+ border-radius: 0 0 8px 8px;
+}
+
+.react-colorful__interactive {
+ position: absolute;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ border-radius: inherit;
+ outline: none;
+ /* Don't trigger the default scrolling behavior when the event is originating from this element */
+ touch-action: none;
+}
+
+.react-colorful__pointer {
+ position: absolute;
+ z-index: 1;
+ box-sizing: border-box;
+ width: 28px;
+ height: 28px;
+ transform: translate(-50%, -50%);
+ background-color: hsl(0, 0%, 81%);
+ border: 2px solid hsl(0, 0%, 81%);
+ border-radius: 50%;
+ box-shadow: 0 2px 5px hsla(0, 0%, 0%, 0.4);
+}
+
+.react-colorful__interactive:focus .react-colorful__pointer {
+ transform: translate(-50%, -50%) scale(1.1);
+ background-color: hsl(0, 0%, 100%);
+ border-color: hsl(0, 0%, 100%);
+}
+
+/* Chessboard-like pattern for alpha related elements */
+.react-colorful__alpha,
+.react-colorful__alpha-pointer {
+ background-color: hsl(0, 0%, 100%);
+ background-image: url('data:image/svg+xml,');
+}
+
+.react-colorful__saturation-pointer,
+.react-colorful__value-pointer,
+.react-colorful__hue-pointer,
+.react-colorful__r-pointer,
+.react-colorful__g-pointer,
+.react-colorful__b-pointer {
+ z-index: 1;
+ width: 20px;
+ height: 20px;
+}
+
+/* Display the saturation value pointer over the hue one */
+.react-colorful__saturation_value-pointer {
+ z-index: 3;
+}
diff --git a/tgui/packages/tgui/styles/interfaces/Preferences.scss b/tgui/packages/tgui/styles/interfaces/Preferences.scss
new file mode 100644
index 00000000000..af37eb32bb8
--- /dev/null
+++ b/tgui/packages/tgui/styles/interfaces/Preferences.scss
@@ -0,0 +1,33 @@
+.PreferencesMenu__SaveNotif {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ font-size: 3em;
+ z-index: 10;
+ height: 100px;
+ overflow: hidden;
+ color: #00ba00;
+ user-select: none;
+}
+
+@keyframes slide-in-out {
+ 0% {
+ transform: translate(0, 100px);
+ }
+
+ 50% {
+ transform: translate(0, 0);
+ }
+
+ 100% {
+ transform: translate(0, -100px);
+ }
+}
+
+.PreferencesMenu__SaveNotif__Inner {
+ background-color: rgba(0, 0, 0, 0.8);
+ border-radius: 16px;
+ padding: 16px;
+ animation: slide-in-out 1s ease-in-out forwards;
+}
diff --git a/tgui/packages/tgui/styles/interfaces/VorePanel.scss b/tgui/packages/tgui/styles/interfaces/VorePanel.scss
index 51216dfa1c5..945d28a7d1b 100644
--- a/tgui/packages/tgui/styles/interfaces/VorePanel.scss
+++ b/tgui/packages/tgui/styles/interfaces/VorePanel.scss
@@ -1,4 +1,6 @@
-.VorePanel__fLoating {
+@use '../colors';
+
+.VorePanel__Floating {
height: 200px;
background-color: var(--color-section);
backdrop-filter: var(--blur-medium);
@@ -23,7 +25,44 @@
transition-duration: var(--button-transition);
transition-timing-function: var(--button-transition-timing);
user-select: none;
- background-color: var(--button-background-default);
+
+ --vp-color: currentColor;
+ --vp-background: var(--button-background-default);
+
+ background-color: var(--vp-background);
+ color: var(--vp-color);
+
+ &:hover {
+ background-color: hsl(
+ from var(--vp-background) h s calc(l + var(--adjust-hover))
+ );
+ }
+
+ &.color-bg-transparent {
+ --vp-background: hsl(from var(--vp-color) h s l / 0.2);
+ background-color: var(--vp-background);
+ opacity: 1;
+ }
+}
+
+@each $color-name, $color-value in colors.$color-map {
+ .VorePanel__floatingButton.color-bg-#{$color-name} {
+ --vp-background: #{$color-value};
+
+ @each $low-color in colors.$low-contrast-color-map {
+ @if $color-name == $low-color {
+ --vp-color: var(--color-black);
+ }
+ }
+ }
+}
+
+.VorePanel__floatingButton.VorePanel__noCursor {
+ cursor: default;
+}
+
+.VorePanel__floatingButton.VorePanel__selected {
+ box-shadow: inset 0 0 0 2px var(--color-white);
}
.VorePanel__pasteArea {
@@ -39,3 +78,18 @@
overflow: hidden;
padding: var(--space-m);
}
+
+.VorePanel__Floating {
+ height: 325px;
+ width: 580px;
+ background-color: var(--color-section);
+ backdrop-filter: var(--blur-medium);
+ border: var(--border-thickness-tiny) solid var(--color-border);
+ border-radius: var(--border-radius-medium);
+ box-shadow: var(--shadow-glow-medium) hsla(0, 0%, 0%, 0.5);
+ margin: var(--space-m);
+ color: var(--color-text);
+ overflow-y: scroll;
+ overflow-x: hidden;
+ padding: var(--space-m);
+}
diff --git a/tgui/packages/tgui/styles/main.scss b/tgui/packages/tgui/styles/main.scss
index a3af99db4b8..3ccd66cb131 100644
--- a/tgui/packages/tgui/styles/main.scss
+++ b/tgui/packages/tgui/styles/main.scss
@@ -15,6 +15,16 @@
@include meta.load-css('~tgui-core/styles/all-atomic.scss');
@include meta.load-css('~tgui-core/styles/all-themes.scss');
+// Atomic classes
+@include meta.load-css('./atomic/candystripe.scss');
+@include meta.load-css('./atomic/centered-image.scss');
+@include meta.load-css('./atomic/color.scss');
+@include meta.load-css('./atomic/debug-layout.scss');
+@include meta.load-css('./atomic/fit-text.scss');
+@include meta.load-css('./atomic/links.scss');
+@include meta.load-css('./atomic/outline.scss');
+@include meta.load-css('./atomic/text.scss');
+
// Themes
@include meta.load-css('./themes/azure_default.scss');
@include meta.load-css('./themes/azure_green.scss');
@@ -25,6 +35,7 @@
// Interfaces
@include meta.load-css('./interfaces/AlertModal.scss');
@include meta.load-css('./interfaces/Changelog.scss');
+@include meta.load-css('./interfaces/ColorPicker.scss');
@include meta.load-css('./interfaces/CrewManifest.scss');
@include meta.load-css('./interfaces/Emojipedia.scss');
@include meta.load-css('./interfaces/ExperimentConfigure.scss');
@@ -42,6 +53,7 @@
@include meta.load-css('./interfaces/NuclearBomb.scss');
@include meta.load-css('./interfaces/Orbit.scss');
@include meta.load-css('./interfaces/Paper.scss');
+@include meta.load-css('./interfaces/Preferences.scss');
@include meta.load-css('./interfaces/PreferencesMenu.scss');
@include meta.load-css('./interfaces/RequestManager.scss');
@include meta.load-css('./interfaces/Roulette.scss');
diff --git a/tools/build/package.json b/tools/build/package.json
index d0d45e6d14e..200ac2edbb4 100644
--- a/tools/build/package.json
+++ b/tools/build/package.json
@@ -2,6 +2,6 @@
"private": true,
"type": "module",
"devDependencies": {
- "@types/bun": "^1.2.16"
+ "@types/bun": "^1.3.6"
}
}