mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 08:45:38 +02:00
Compare commits
16 Commits
e0751b368a
...
feat/play
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8aa983834c | ||
|
|
5189bf768a | ||
|
|
b734892c55 | ||
|
|
402e894224 | ||
|
|
fb0cb0b6ca | ||
|
|
4fd339b55f | ||
|
|
1e78238593 | ||
|
|
c994dc112c | ||
|
|
a727a9b710 | ||
|
|
805a8a6115 | ||
|
|
05aa177681 | ||
|
|
421fcb067c | ||
|
|
7dee7e480b | ||
|
|
1a49c709f7 | ||
|
|
90e0533fdd | ||
|
|
058ac24954 |
@@ -30,6 +30,7 @@
|
|||||||
"@solidjs/router": "^0.15.3",
|
"@solidjs/router": "^0.15.3",
|
||||||
"modern-normalize": "^3.0.1",
|
"modern-normalize": "^3.0.1",
|
||||||
"solid-js": "^1.9.5",
|
"solid-js": "^1.9.5",
|
||||||
|
"solid-notifications": "^1.1.2",
|
||||||
"valibot": "^1.0.0-rc.3",
|
"valibot": "^1.0.0-rc.3",
|
||||||
"zod": "^3.24.2"
|
"zod": "^3.24.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import '@fontsource/geist-sans/600.css';
|
|||||||
import '@fontsource/geist-sans/700.css';
|
import '@fontsource/geist-sans/700.css';
|
||||||
import '@fontsource/geist-sans/800.css';
|
import '@fontsource/geist-sans/800.css';
|
||||||
import '@fontsource/geist-sans/900.css';
|
import '@fontsource/geist-sans/900.css';
|
||||||
|
import { PlayComponent } from './pages/play';
|
||||||
import { styled } from "@macaron-css/solid";
|
import { styled } from "@macaron-css/solid";
|
||||||
import { useStorage } from './providers/account';
|
import { useStorage } from './providers/account';
|
||||||
import { CreateTeamComponent } from './pages/new';
|
import { CreateTeamComponent } from './pages/new';
|
||||||
@@ -14,6 +15,7 @@ import { AuthProvider, useAuth } from './providers/auth';
|
|||||||
import { Navigate, Route, Router } from "@solidjs/router";
|
import { Navigate, Route, Router } from "@solidjs/router";
|
||||||
import { globalStyle, macaron$ } from "@macaron-css/core";
|
import { globalStyle, macaron$ } from "@macaron-css/core";
|
||||||
import { Component, createSignal, Match, onCleanup, Switch } from 'solid-js';
|
import { Component, createSignal, Match, onCleanup, Switch } from 'solid-js';
|
||||||
|
import TestComponent from './pages/test';
|
||||||
|
|
||||||
const Root = styled("div", {
|
const Root = styled("div", {
|
||||||
base: {
|
base: {
|
||||||
@@ -93,6 +95,7 @@ export const App: Component = () => {
|
|||||||
// props.children
|
// props.children
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
<Route path="play/:id" component={PlayComponent} />
|
||||||
<Route path="new" component={CreateTeamComponent} />
|
<Route path="new" component={CreateTeamComponent} />
|
||||||
<Route
|
<Route
|
||||||
path="/"
|
path="/"
|
||||||
|
|||||||
80
packages/www/src/components/Modal.tsx
Normal file
80
packages/www/src/components/Modal.tsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { Component, JSX, Show, createSignal } from "solid-js";
|
||||||
|
import { Portal } from "solid-js/web";
|
||||||
|
import { styled } from "@macaron-css/solid";
|
||||||
|
import { theme } from "@nestri/www/ui/theme";
|
||||||
|
|
||||||
|
const ModalContainer = styled("div", {
|
||||||
|
base: {
|
||||||
|
width: "100%",
|
||||||
|
maxWidth: 370,
|
||||||
|
maxHeight: "75vh",
|
||||||
|
height: "auto",
|
||||||
|
borderRadius: 12,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderStyle: "solid",
|
||||||
|
borderColor: theme.color.gray.d400,
|
||||||
|
backgroundColor: theme.color.gray.d200,
|
||||||
|
boxShadow: theme.color.boxShadow,
|
||||||
|
backdropFilter: "blur(20px)",
|
||||||
|
padding: "20px 25px"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export interface ModalProps {
|
||||||
|
isOpen?: boolean;
|
||||||
|
onClose?: ((value: boolean) => void) | (() => void);
|
||||||
|
children: JSX.Element;
|
||||||
|
mountPoint?: HTMLElement;
|
||||||
|
containerClass?: string;
|
||||||
|
overlayClass?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createModalController() {
|
||||||
|
const [isOpen, setIsOpen] = createSignal(false);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isOpen,
|
||||||
|
open: () => setIsOpen(true),
|
||||||
|
close: () => setIsOpen(false),
|
||||||
|
toggle: () => setIsOpen(!isOpen()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Modal: Component<ModalProps> = (props) => {
|
||||||
|
const mountPoint = props.mountPoint || document.getElementById("styled") || document.body;
|
||||||
|
const isOpen = () => props.isOpen ?? false;
|
||||||
|
|
||||||
|
const defaultOverlayStyle = `
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 50;
|
||||||
|
`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal mount={mountPoint}>
|
||||||
|
<Show when={isOpen()}>
|
||||||
|
<div
|
||||||
|
class={props.overlayClass}
|
||||||
|
style={!props.overlayClass ? defaultOverlayStyle : undefined}
|
||||||
|
onClick={(e) => {
|
||||||
|
if (e.target === e.currentTarget && props.onClose) {
|
||||||
|
if (props.onClose.length > 0) {
|
||||||
|
(props.onClose as (value: boolean) => void)(false);
|
||||||
|
} else {
|
||||||
|
(props.onClose as () => void)();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ModalContainer>
|
||||||
|
{props.children}
|
||||||
|
</ModalContainer>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -8,6 +8,7 @@ import { render } from "solid-js/web";
|
|||||||
import "modern-normalize/modern-normalize.css";
|
import "modern-normalize/modern-normalize.css";
|
||||||
import { App } from "./App";
|
import { App } from "./App";
|
||||||
import { StorageProvider } from "./providers/account";
|
import { StorageProvider } from "./providers/account";
|
||||||
|
// import { ToastProvider, Toaster } from "solid-notifications";
|
||||||
|
|
||||||
const root = document.getElementById("root");
|
const root = document.getElementById("root");
|
||||||
|
|
||||||
@@ -19,9 +20,12 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
|||||||
|
|
||||||
render(
|
render(
|
||||||
() => (
|
() => (
|
||||||
|
// <ToastProvider>
|
||||||
|
// <Toaster />
|
||||||
<StorageProvider>
|
<StorageProvider>
|
||||||
<App />
|
<App />
|
||||||
</StorageProvider>
|
</StorageProvider>
|
||||||
|
// </ToastProvider>
|
||||||
),
|
),
|
||||||
root!
|
root!
|
||||||
);
|
);
|
||||||
326
packages/www/src/pages/play.tsx
Normal file
326
packages/www/src/pages/play.tsx
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
// FIXME: We need to make from the modal a reusable component
|
||||||
|
// FIXME: The mousepointer lock is somehow shifted when the window gets resized
|
||||||
|
import { Text } from "@nestri/www/ui/text";
|
||||||
|
import { createSignal, createEffect, onCleanup, onMount, Show } from "solid-js";
|
||||||
|
import { Portal } from "solid-js/web";
|
||||||
|
import { useParams } from "@solidjs/router";
|
||||||
|
import { Keyboard, Mouse, WebRTCStream } from "@nestri/input";
|
||||||
|
import { Container, FullScreen } from "@nestri/www/ui/layout";
|
||||||
|
import { styled } from "@macaron-css/solid";
|
||||||
|
import { lightClass, theme, darkClass } from "@nestri/www/ui/theme";
|
||||||
|
import { Modal, createModalController } from "../components/Modal";
|
||||||
|
|
||||||
|
const Canvas = styled("canvas", {
|
||||||
|
base: {
|
||||||
|
aspectRatio: 16 / 9,
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
objectFit: "contain",
|
||||||
|
maxHeight: "100vh",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const Button = styled("button", {
|
||||||
|
base: {
|
||||||
|
outline: "none",
|
||||||
|
width: "100%",
|
||||||
|
backgroundColor: theme.color.background.d100,
|
||||||
|
padding: "12px 16px",
|
||||||
|
borderRadius: 10,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderStyle: "solid",
|
||||||
|
borderColor: theme.color.gray.d500,
|
||||||
|
":hover": {
|
||||||
|
backgroundColor: theme.color.gray.d300,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export function PlayComponent() {
|
||||||
|
const params = useParams();
|
||||||
|
const id = params.id;
|
||||||
|
|
||||||
|
const [showBannerModal, setShowBannerModal] = createSignal(false);
|
||||||
|
const [showButtonModal, setShowButtonModal] = createSignal(false);
|
||||||
|
const [gamepadConnected, setGamepadConnected] = createSignal(false);
|
||||||
|
const [buttonPressed, setButtonPressed] = createSignal(null);
|
||||||
|
const [leftStickX, setLeftStickX] = createSignal(0);
|
||||||
|
const [leftStickY, setLeftStickY] = createSignal(0);
|
||||||
|
const [hasStream, setHasStream] = createSignal(false);
|
||||||
|
const [nestriLock, setNestriLock] = createSignal(false);
|
||||||
|
const [showOffline, setShowOffline] = createSignal(false);
|
||||||
|
|
||||||
|
const [canvas, setCanvas] = createSignal<HTMLCanvasElement | undefined>(undefined);
|
||||||
|
let video: HTMLVideoElement;
|
||||||
|
let webrtc: WebRTCStream;
|
||||||
|
let nestriMouse: Mouse, nestriKeyboard: Keyboard;
|
||||||
|
|
||||||
|
const { Modal, openModal } = createModal();
|
||||||
|
const { WelcomeModal, openWelcomeModal } = createWelcomeModal();
|
||||||
|
|
||||||
|
const initializeInputDevices = () => {
|
||||||
|
const canvasElement = canvas();
|
||||||
|
if (!canvasElement || !webrtc) return;
|
||||||
|
try {
|
||||||
|
nestriMouse = new Mouse({ canvas: canvasElement, webrtc });
|
||||||
|
nestriKeyboard = new Keyboard({ canvas: canvasElement, webrtc });
|
||||||
|
console.log("Input devices initialized successfully");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to initialize input devices:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*const initializeGamepad = () => {
|
||||||
|
console.log("Initializing gamepad...");
|
||||||
|
|
||||||
|
const updateGamepadState = () => {
|
||||||
|
const gamepads = navigator.getGamepads();
|
||||||
|
const gamepad = gamepads[0];
|
||||||
|
if (gamepad) {
|
||||||
|
setButtonPressed(gamepad.buttons.findIndex(btn => btn.pressed) !== -1 ? "Button pressed" : null);
|
||||||
|
setLeftStickX(Number(gamepad.axes[0].toFixed(2)));
|
||||||
|
setLeftStickY(Number(gamepad.axes[1].toFixed(2)));
|
||||||
|
}
|
||||||
|
requestAnimationFrame(updateGamepadState);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("gamepadconnected", () => {
|
||||||
|
setGamepadConnected(true);
|
||||||
|
console.log("Gamepad connected!");
|
||||||
|
updateGamepadState();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("gamepaddisconnected", () => {
|
||||||
|
setGamepadConnected(false);
|
||||||
|
console.log("Gamepad disconnected!");
|
||||||
|
});
|
||||||
|
};*/
|
||||||
|
|
||||||
|
const lockPlay = async () => {
|
||||||
|
const canvasElement = canvas();
|
||||||
|
if (!canvasElement || !hasStream()) return;
|
||||||
|
try {
|
||||||
|
await canvasElement.requestPointerLock();
|
||||||
|
await canvasElement.requestFullscreen();
|
||||||
|
//initializeGamepad();
|
||||||
|
|
||||||
|
if (document.fullscreenElement !== null) {
|
||||||
|
if ('keyboard' in navigator && 'lock' in (navigator.keyboard as any)) {
|
||||||
|
const keys = [
|
||||||
|
"AltLeft", "AltRight", "Tab", "Escape",
|
||||||
|
"ContextMenu", "MetaLeft", "MetaRight"
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
await (navigator.keyboard as any).lock(keys);
|
||||||
|
setNestriLock(true);
|
||||||
|
console.log("Keyboard lock acquired");
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Keyboard lock failed:", e);
|
||||||
|
setNestriLock(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error during lock sequence:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setupPointerLockListener = () => {
|
||||||
|
document.addEventListener("pointerlockchange", () => {
|
||||||
|
const canvasElement = canvas();
|
||||||
|
if (!canvasElement) return;
|
||||||
|
if (document.pointerLockElement === canvasElement) {
|
||||||
|
initializeInputDevices();
|
||||||
|
} else {
|
||||||
|
console.log("Pointer lock lost Show Banner Modal:", showBannerModal());
|
||||||
|
if (!showBannerModal()) {
|
||||||
|
console.log("Pointer lock lost, showing banner");
|
||||||
|
const playing = sessionStorage.getItem("showedBanner");
|
||||||
|
setShowBannerModal(!playing || playing !== "true");
|
||||||
|
openWelcomeModal();
|
||||||
|
|
||||||
|
if (playing) {
|
||||||
|
setShowButtonModal(true);
|
||||||
|
openModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
nestriKeyboard?.dispose();
|
||||||
|
nestriMouse?.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleVideoInput = async () => {
|
||||||
|
const canvasElement = canvas();
|
||||||
|
if (!video || !canvasElement) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
await video.play();
|
||||||
|
if (canvasElement && video) {
|
||||||
|
canvasElement.width = video.videoWidth;
|
||||||
|
canvasElement.height = video.videoHeight;
|
||||||
|
|
||||||
|
|
||||||
|
const ctx = canvasElement.getContext("2d");
|
||||||
|
const renderer = () => {
|
||||||
|
if (ctx && hasStream() && video) {
|
||||||
|
ctx.drawImage(video, 0, 0);
|
||||||
|
video.requestVideoFrameCallback(renderer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
video.requestVideoFrameCallback(renderer);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error playing video:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
const canvasElement = canvas();
|
||||||
|
if (!canvasElement) return;
|
||||||
|
|
||||||
|
setupPointerLockListener();
|
||||||
|
video = document.createElement("video");
|
||||||
|
video.style.visibility = "hidden";
|
||||||
|
webrtc = new WebRTCStream("https://relay.dathorse.com", id, async (mediaStream) => {
|
||||||
|
if (video && mediaStream) {
|
||||||
|
video.srcObject = mediaStream;
|
||||||
|
setHasStream(true);
|
||||||
|
setShowOffline(false);
|
||||||
|
|
||||||
|
const playing = sessionStorage.getItem("showedBanner")
|
||||||
|
console.log("Playing:", playing);
|
||||||
|
if (!playing || playing != "true") {
|
||||||
|
console.log("Showing banner: ", showBannerModal());
|
||||||
|
if (!showBannerModal()) {
|
||||||
|
setShowBannerModal(false)
|
||||||
|
openWelcomeModal();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!showButtonModal()) {
|
||||||
|
setShowButtonModal(true)
|
||||||
|
openModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await handleVideoInput();
|
||||||
|
} else if (mediaStream === null) {
|
||||||
|
console.log("MediaStream is null, Room is offline");
|
||||||
|
setShowOffline(true);
|
||||||
|
setHasStream(false);
|
||||||
|
|
||||||
|
const ctx = canvasElement.getContext("2d");
|
||||||
|
if (ctx) ctx.clearRect(0, 0, canvasElement.width, canvasElement.height);
|
||||||
|
} else if (video && video.srcObject !== null) {
|
||||||
|
setHasStream(true);
|
||||||
|
setShowOffline(true);
|
||||||
|
await handleVideoInput();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
nestriKeyboard?.dispose();
|
||||||
|
nestriMouse?.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
return (<FullScreen>
|
||||||
|
{showOffline() ? (
|
||||||
|
<div class="w-screen h-screen flex justify-center items-center">
|
||||||
|
<span class="text-2xl font-semibold flex items-center gap-2">Offline</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Canvas ref={setCanvas} onClick={lockPlay} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<WelcomeModal
|
||||||
|
show={showBannerModal}
|
||||||
|
setShow={setShowBannerModal}
|
||||||
|
closeOnBackdropClick={false}
|
||||||
|
handleVideoInput={handleVideoInput}
|
||||||
|
lockPlay={lockPlay} />
|
||||||
|
|
||||||
|
<Modal show={showButtonModal}
|
||||||
|
setShow={setShowButtonModal}
|
||||||
|
closeOnBackdropClick={false}
|
||||||
|
handleVideoInput={handleVideoInput}
|
||||||
|
lockPlay={lockPlay} />
|
||||||
|
</FullScreen>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ModalProps {
|
||||||
|
show: () => boolean;
|
||||||
|
setShow: (value: boolean) => void;
|
||||||
|
closeOnBackdropClick?: boolean;
|
||||||
|
handleVideoInput?: () => Promise<void>;
|
||||||
|
lockPlay?: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type GameModalProps = ModalProps & {
|
||||||
|
handleVideoInput?: () => Promise<void>;
|
||||||
|
lockPlay?: () => Promise<void>;
|
||||||
|
setShow?: (show: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWelcomeModal() {
|
||||||
|
const controller = createModalController();
|
||||||
|
|
||||||
|
return {
|
||||||
|
openWelcomeModal: controller.open,
|
||||||
|
WelcomeModal(props: GameModalProps) {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={controller.isOpen()}
|
||||||
|
onClose={controller.close}
|
||||||
|
>
|
||||||
|
<div style={{ display: "flex", "flex-direction": "column", gap: "12px" }}>
|
||||||
|
Happy that you use Nestri!
|
||||||
|
<Button onClick={async () => {
|
||||||
|
sessionStorage.setItem("showedBanner", "true");
|
||||||
|
await props.handleVideoInput?.();
|
||||||
|
await props.lockPlay?.();
|
||||||
|
controller.close();
|
||||||
|
}}>Let's go</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createModal() {
|
||||||
|
const controller = createModalController();
|
||||||
|
|
||||||
|
return {
|
||||||
|
openModal: controller.open,
|
||||||
|
Modal(props: GameModalProps) {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={controller.isOpen()}
|
||||||
|
onClose={controller.close}
|
||||||
|
>
|
||||||
|
<div style={{ display: "flex", "flex-direction": "column", gap: "12px" }}>
|
||||||
|
<Button onClick={async () => {
|
||||||
|
props.setShow?.(false);
|
||||||
|
await props.handleVideoInput?.();
|
||||||
|
await props.lockPlay?.();
|
||||||
|
controller.close();
|
||||||
|
}}>Continue Playing</Button>
|
||||||
|
<Button onClick={controller.close}>Shutdown Nestri</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
18
packages/www/src/pages/test.tsx
Normal file
18
packages/www/src/pages/test.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { styled } from "@macaron-css/solid";
|
||||||
|
import { theme } from "../ui/theme";
|
||||||
|
|
||||||
|
|
||||||
|
const Testing = styled("div", {
|
||||||
|
base: {
|
||||||
|
height: "100%",
|
||||||
|
width: "100%",
|
||||||
|
position: "fixed",
|
||||||
|
backgroundColor: theme.color.blue.d600
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default function TestComponent() {
|
||||||
|
return (
|
||||||
|
<Testing />
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -241,6 +241,7 @@ const light = (() => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const boxShadow = "0 0 0 1px rgba(19,21,23,0.08), 0 3.3px 2.7px rgba(0,0,0,.03),0 8.3px 6.9px rgba(0,0,0,.04),0 17px 14.2px rgba(0,0,0,.05),0 35px 29.2px rgba(0,0,0,.06),0px -4px 4px 0px rgba(0,0,0,.07) inset";
|
||||||
return {
|
return {
|
||||||
gray,
|
gray,
|
||||||
blue,
|
blue,
|
||||||
@@ -256,6 +257,7 @@ const light = (() => {
|
|||||||
focusBorder,
|
focusBorder,
|
||||||
focusColor,
|
focusColor,
|
||||||
d1000,
|
d1000,
|
||||||
|
boxShadow,
|
||||||
brand,
|
brand,
|
||||||
text
|
text
|
||||||
};
|
};
|
||||||
@@ -403,6 +405,8 @@ const dark = (() => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const boxShadow = "0 0 0 1px rgba(255,255,255,0.08), 0 3.3px 2.7px rgba(0,0,0,.1),0 8.3px 6.9px rgba(0,0,0,.13),0 17px 14.2px rgba(0,0,0,.17),0 35px 29.2px rgba(0,0,0,.22),0px -4px 4px 0px rgba(0,0,0,.04) inset";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
gray,
|
gray,
|
||||||
blue,
|
blue,
|
||||||
@@ -419,6 +423,7 @@ const dark = (() => {
|
|||||||
focusColor,
|
focusColor,
|
||||||
d1000,
|
d1000,
|
||||||
text,
|
text,
|
||||||
|
boxShadow,
|
||||||
brand
|
brand
|
||||||
};
|
};
|
||||||
})()
|
})()
|
||||||
|
|||||||
Reference in New Issue
Block a user