diff --git a/frontend/src/styles/index.scss b/frontend/src/styles/index.scss
index cfb4b0880562..26504cdd4732 100644
--- a/frontend/src/styles/index.scss
+++ b/frontend/src/styles/index.scss
@@ -18,7 +18,7 @@
@layer custom-styles {
@import "buttons", "404", "ads", "account", "animations", "caret",
"commandline", "core", "fonts", "inputs", "keymap", "login", "monkey",
- "nav", "notifications", "popups", "scroll", "settings", "account-settings",
+ "notifications", "popups", "scroll", "settings", "account-settings",
"leaderboards", "test", "loading", "friends", "media-queries";
.chartCanvas {
diff --git a/frontend/src/styles/media-queries-blue.scss b/frontend/src/styles/media-queries-blue.scss
index f27fa33e30ad..b3634b288738 100644
--- a/frontend/src/styles/media-queries-blue.scss
+++ b/frontend/src/styles/media-queries-blue.scss
@@ -18,15 +18,6 @@
}
}
}
- header {
- nav {
- .textButton.view-account {
- .text {
- display: none;
- }
- }
- }
- }
.page404 {
.content {
grid-template-columns: 300px;
diff --git a/frontend/src/styles/media-queries-brown.scss b/frontend/src/styles/media-queries-brown.scss
index 0dec474c9c7a..d1127446d29f 100644
--- a/frontend/src/styles/media-queries-brown.scss
+++ b/frontend/src/styles/media-queries-brown.scss
@@ -2,13 +2,6 @@
@use "./media.scss" as *;
@include media-query(brown) {
- header {
- nav {
- font-size: 0.8rem;
- line-height: 0.8rem;
- gap: 0.1rem;
- }
- }
.page404 {
.content {
grid-template-columns: 1fr;
diff --git a/frontend/src/styles/media-queries-gray.scss b/frontend/src/styles/media-queries-gray.scss
index 4cdd96e29510..9f311306abaf 100644
--- a/frontend/src/styles/media-queries-gray.scss
+++ b/frontend/src/styles/media-queries-gray.scss
@@ -14,10 +14,4 @@
display: none;
}
}
- header {
- gap: 0.25rem;
- nav .textButton {
- padding: 0.5em 0.25em;
- }
- }
}
diff --git a/frontend/src/styles/media-queries-green.scss b/frontend/src/styles/media-queries-green.scss
index 68e886cf336e..556c9bacb757 100644
--- a/frontend/src/styles/media-queries-green.scss
+++ b/frontend/src/styles/media-queries-green.scss
@@ -8,21 +8,6 @@
font-size: 0.7rem;
--horizontalPadding: 0.6em;
}
- header {
- #logo {
- .text {
- font-size: 1.5rem;
- .top {
- display: none;
- }
- }
- }
- nav {
- .textButton.view-account {
- gap: 0.5em;
- }
- }
- }
.pageAccountSettings .main {
gap: 2rem;
.right .tab .section {
diff --git a/frontend/src/styles/media-queries-purple.scss b/frontend/src/styles/media-queries-purple.scss
index 3020530eade0..908548af8a6d 100644
--- a/frontend/src/styles/media-queries-purple.scss
+++ b/frontend/src/styles/media-queries-purple.scss
@@ -39,24 +39,6 @@
}
}
}
- header {
- nav {
- font-size: 0.9rem;
- line-height: 0.9rem;
- gap: 0.25rem;
- .view-account {
- .text {
- display: none;
- }
- }
- }
- #logo {
- grid-template-columns: 1fr;
- .text {
- display: none;
- }
- }
- }
.pageLogin {
flex-direction: column;
gap: 4rem;
diff --git a/frontend/src/styles/nav.scss b/frontend/src/styles/nav.scss
deleted file mode 100644
index e3c018f55bfe..000000000000
--- a/frontend/src/styles/nav.scss
+++ /dev/null
@@ -1,458 +0,0 @@
-nav {
- font-size: 1rem;
- line-height: 1rem;
- color: var(--sub-color);
- display: flex;
- gap: 0.5rem;
- width: 100%;
-
- .spacer {
- flex-grow: 1;
- }
-
- button.showAlerts {
- position: relative;
- }
-
- .textButton {
- position: relative;
- .text {
- font-size: 0.75em;
- align-self: center;
- .fas {
- margin-left: 0.33em;
- }
- }
-
- .icon,
- .spinner {
- width: 1.25em;
- height: 1.25em;
- display: grid;
- place-content: center center;
- }
- .spinner {
- opacity: 0;
- transition: opacity 0.125s;
- }
- }
-
- .accountButtonAndMenu {
- position: relative;
- .view-account {
- position: relative;
- align-items: center;
- gap: 0.33em;
- display: grid;
- grid-auto-flow: column;
-
- .notificationBubble {
- left: 3em;
- top: 0.5em;
- }
-
- .spinner,
- .avatar {
- grid-column: 1/2;
- grid-row: 1/2;
- place-self: center center;
- }
-
- .discordImage {
- width: 1.25em;
- height: 1.25em;
- }
-
- .levelAndBar {
- transition: 0.125s;
- }
-
- .level {
- width: max-content;
- font-size: 0.65em;
- line-height: 0.65em;
- align-self: center;
- color: var(--bg-color);
- background: var(--sub-color);
- padding: 0.4em 0.4em;
- border-radius: calc(var(--roundness) / 2);
- }
-
- .xpBar {
- z-index: 5;
- opacity: 0;
- pointer-events: none;
- position: absolute;
- height: 0.25em;
- bottom: -0.5em;
- width: 100%;
- min-width: 16ch;
- right: 0;
- background: var(--sub-alt-color);
- border-radius: var(--roundness);
- grid-template-columns: auto 2.5em;
- .bar {
- left: 0;
- width: 0%;
- height: 100%;
- background: var(--main-color);
- border-radius: var(--roundness);
- }
- .xpBreakdown {
- backdrop-filter: blur(1em);
- /*background: linear-gradient(
- 90deg,
- transparent 0%,
- var(--bg-color) 10%
- );*/
- min-width: 100%;
- width: max-content;
- position: absolute;
- color: var(--text-color);
- display: grid;
- // gap: 0.5em;
- //justify-items: center;
- // width: 32ch;
- justify-self: end;
- right: 0;
- padding: 0.5em;
- border-radius: var(--roundness);
-
- .total {
- text-align: right;
- font-size: 1em;
- color: var(--main-color);
- width: max-content;
- justify-self: end;
- }
- .divider {
- width: 100%;
- height: 0.1rem;
- background: var(--sub-alt-color);
- border-radius: var(--roundness);
- opacity: 0.5;
- }
- .list {
- margin-top: 0.5em;
- .line {
- font-size: 0.8em;
- display: grid;
- grid-template-columns: auto 10ch;
- gap: 0.5em;
-
- & div {
- text-align: right;
- }
-
- .positive {
- color: var(--main-color);
- }
- .negative {
- color: var(--error-color);
- }
- .total {
- font-weight: bold;
- }
- }
- }
- }
- }
- &:hover {
- .level {
- background-color: var(--text-color);
- }
- .userFlags {
- color: var(--text-color);
- }
- }
- &:focus-within {
- z-index: 10;
- }
- }
- .menu {
- // display: none;
- pointer-events: none;
- z-index: 5;
- opacity: 0;
- position: absolute;
- top: 99%;
- right: 0;
- font-size: 0.75rem;
- min-width: 23ch;
- width: 100%;
- transition: opacity 0.125s;
-
- .spacer {
- width: 100%;
- height: 0.5em;
- background: transparent;
- }
-
- .items {
- box-shadow: 0 0 0 0.5em var(--bg-color);
- background: var(--sub-alt-color);
- display: grid;
- border-radius: var(--roundness);
- gap: 0.25em;
-
- a,
- button {
- justify-items: start;
- justify-content: left;
- padding-left: 0;
- border-radius: 0;
- gap: 0;
- i {
- margin-right: 0.7em;
- margin-left: 0.9em;
- }
- &:last-child {
- border-radius: 0 0 var(--roundness) var(--roundness);
- }
- &:first-child {
- border-radius: var(--roundness) var(--roundness) 0 0;
- }
- &:focus-visible {
- border-radius: var(--roundness);
- }
- }
-
- & .goToFriends {
- display: grid;
- grid-template-columns: auto 1fr auto;
- text-align: left;
- .notificationBubble {
- margin-right: 0.5em;
- position: unset;
- box-shadow: 0 0 0 0.5em var(--sub-alt-color);
- display: block;
- }
- }
- }
- }
- &:hover,
- &:focus-within {
- & .menu {
- pointer-events: all;
- opacity: 1;
- }
- // .view-account {
- // background: var(--sub-alt-color);
- // }
- }
- }
-
- .separator {
- width: 2px;
- height: 1rem;
- background-color: var(--sub-color);
- }
-}
-
-header {
- grid-template-areas: "logo menu";
- line-height: 2.3rem;
- font-size: 2.3rem;
- /* text-align: center; */
- // transition: 0.25s;
- // padding: 0 5px;
- display: grid;
- grid-auto-flow: column;
- grid-template-columns: auto 1fr;
- z-index: 3;
- align-items: center;
- gap: 0.5rem;
- -webkit-user-select: none;
- user-select: none;
-
- #logo {
- // margin-bottom: 0.6rem;
- cursor: pointer;
- display: grid;
- grid-template-columns: auto 1fr;
- gap: 0.5rem;
- transition: none;
- text-decoration: none;
- color: var(--text-color);
- padding: 0.35rem 0.25rem;
- margin-left: -0.25rem;
- margin-right: -0.25rem;
-
- .icon {
- width: 2.5rem;
- display: grid;
- align-items: center;
- background-color: transparent;
- // margin-bottom: 0.15rem;
- svg path {
- transition: 0.25s;
- fill: var(--main-color);
- }
- }
- .text {
- .top {
- position: absolute;
- left: 0.35em;
- font-size: 0.325em;
- line-height: 0.325em;
- color: var(--sub-color);
- transition:
- color 0.125s,
- opacity 0.125s;
- }
- position: relative;
- font-size: 2rem;
- line-height: 2rem;
- font-family: "Lexend Deca", sans-serif;
- transition: color 0.25s;
- font-weight: unset;
- margin-block-start: unset;
- margin-block-end: unset;
- margin-top: -0.23em;
- }
- white-space: nowrap;
- -webkit-user-select: none;
- user-select: none;
-
- .bottom {
- // margin-left: -0.15rem;
- // color: var(--main-color);
- // transition: 0.25s;
- // cursor: pointer;
- font-size: 0.4em;
- position: absolute;
- right: -10px;
- bottom: -20px;
- color: var(--main-color);
- transform: rotate(-5deg);
- animation: pulse 1.5s infinite;
- }
-
- @keyframes pulse {
- 0% {
- transform: rotate(-5deg) scale(1);
- }
- 50% {
- transform: rotate(-5deg) scale(1.1);
- }
- 100% {
- transform: rotate(-5deg) scale(1);
- }
- }
- &:focus-visible {
- outline: none;
- box-shadow:
- 0 0 0 0.1rem var(--bg-color),
- 0 0 0 0.2rem var(--text-color);
- border-radius: var(--roundness);
- .text .top {
- opacity: 0;
- transition: none;
- }
- }
- }
-
- .config {
- grid-area: config;
- transition: 0.125s;
- .mobileConfig {
- display: none;
- .textButton {
- margin-right: -1rem;
- padding: 0.5rem 1rem;
- font-size: 2rem;
- }
- }
-
- .desktopConfig {
- justify-self: right;
- display: grid;
- // grid-auto-flow: row;
- grid-template-rows: 0.7rem 0.7rem 0.7rem;
- grid-gap: 0.2rem;
- // width: min-content;
- // width: -moz-min-content;
- // transition: 0.25s;
- /* margin-bottom: 0.1rem; */
- justify-items: self-end;
-
- .group {
- // transition: 0.25s;
-
- .title {
- color: var(--sub-color);
- font-size: 0.5rem;
- line-height: 0.5rem;
- margin-bottom: 0.15rem;
- }
-
- .buttons {
- display: flex;
- .textButton {
- font-size: 0.7rem;
- line-height: 0.7rem;
- padding: 0px 0.15rem;
- }
- }
- &.disabled {
- pointer-events: none;
- opacity: 0.25;
- }
- }
-
- .punctuationMode {
- margin-bottom: -0.1rem;
- }
-
- .numbersMode {
- margin-bottom: -0.1rem;
- }
- }
- }
- //top focus
- &.focus {
- color: var(--sub-color) !important;
-
- .notificationBubble {
- opacity: 0;
- }
-
- .result {
- opacity: 0 !important;
- }
-
- .icon svg path {
- fill: var(--sub-color) !important;
- }
-
- #logo .text {
- color: var(--sub-color) !important;
- // opacity: 0 !important;
- }
-
- #logo .top {
- opacity: 0 !important;
- }
-
- #logo .bottom {
- opacity: 0 !important;
- }
-
- .config {
- opacity: 0 !important;
- }
-
- nav {
- color: transparent;
-
- .textButton,
- button {
- color: transparent !important;
- }
- .avatar,
- .levelAndBar {
- opacity: 0 !important;
- }
- }
- }
-}
diff --git a/frontend/src/ts/components/common/Bar.tsx b/frontend/src/ts/components/common/Bar.tsx
new file mode 100644
index 000000000000..2e0a0227d802
--- /dev/null
+++ b/frontend/src/ts/components/common/Bar.tsx
@@ -0,0 +1,46 @@
+import { JSXElement } from "solid-js";
+
+import { Anime } from "./anime";
+
+type Props = {
+ percent: number;
+ fill: "main" | "text";
+ bg: "bg" | "sub-alt";
+ showPercentageOnHover?: boolean;
+ animationDuration?: number;
+ animationEase?: string;
+};
+
+const bgClassMap: Record
= {
+ bg: "bg-bg",
+ "sub-alt": "bg-sub-alt",
+};
+
+const fillClassMap: Record = {
+ main: "bg-main",
+ text: "bg-text",
+};
+
+export function Bar(props: Props): JSXElement {
+ return (
+
+ );
+}
diff --git a/frontend/src/ts/components/common/Button.tsx b/frontend/src/ts/components/common/Button.tsx
index e5b15a9b5b26..16d42da42f09 100644
--- a/frontend/src/ts/components/common/Button.tsx
+++ b/frontend/src/ts/components/common/Button.tsx
@@ -15,10 +15,13 @@ type BaseProps = {
| string
| { text: string; position: "up" | "down" | "left" | "right" };
"router-link"?: true;
+ onClick?: () => void;
+ onMouseEnter?: () => void;
+ onMouseLeave?: () => void;
+ dataset?: Record;
};
type ButtonProps = BaseProps & {
- onClick: () => void;
href?: never;
sameTarget?: true;
active?: boolean;
@@ -27,7 +30,7 @@ type ButtonProps = BaseProps & {
type AnchorProps = BaseProps & {
href: string;
- onClick?: never;
+ // onClick?: never;
disabled?: never;
};
@@ -58,7 +61,7 @@ export function Button(props: ButtonProps | AnchorProps): JSXElement {
const getClasses = (): string => {
return cn(
- "inline-flex h-min cursor-pointer appearance-none items-center justify-center gap-[0.5em] rounded border-0 p-[0.5em] text-center leading-[1.25] text-text transition-colors transition-opacity duration-125 ease-in-out select-none",
+ "inline-flex h-min cursor-pointer appearance-none items-center justify-center gap-[0.5em] rounded border-0 p-[0.5em] text-center leading-[1.25] text-text transition-[color,background,opacity] duration-125 ease-in-out select-none",
"focus-visible:shadow-[0_0_0_0.1rem_var(--bg-color),_0_0_0_0.2rem_var(--text-color)] focus-visible:outline-none",
{
"bg-sub-alt hover:bg-text hover:text-bg": props.type !== "text",
@@ -94,6 +97,10 @@ export function Button(props: ButtonProps | AnchorProps): JSXElement {
}
{...ariaLabel()}
{...(props["router-link"] ? { "router-link": "" } : {})}
+ onClick={() => props.onClick?.()}
+ onMouseEnter={() => props.onMouseEnter?.()}
+ onMouseLeave={() => props.onMouseLeave?.()}
+ {...props.dataset}
>
{content}
@@ -103,9 +110,12 @@ export function Button(props: ButtonProps | AnchorProps): JSXElement {
type="button"
class={getClasses()}
onClick={() => props.onClick?.()}
+ onMouseEnter={() => props.onMouseEnter?.()}
+ onMouseLeave={() => props.onMouseLeave?.()}
{...ariaLabel()}
{...(props["router-link"] ? { "router-link": "" } : {})}
disabled={props.disabled ?? false}
+ {...props.dataset}
>
{content}
diff --git a/frontend/src/ts/components/common/Conditional.tsx b/frontend/src/ts/components/common/Conditional.tsx
index 698cc06aeb11..97117946474e 100644
--- a/frontend/src/ts/components/common/Conditional.tsx
+++ b/frontend/src/ts/components/common/Conditional.tsx
@@ -1,13 +1,15 @@
-import { JSXElement, Show } from "solid-js";
+import { Accessor, JSXElement, Show } from "solid-js";
-export function Conditional(props: {
- if: boolean;
- then: JSXElement;
+export function Conditional(props: {
+ if: T;
+ then: JSXElement | ((value: Accessor>) => JSXElement);
else?: JSXElement;
}): JSXElement {
return (
- {props.then}
+ {(value) =>
+ typeof props.then === "function" ? props.then(value) : props.then
+ }
);
}
diff --git a/frontend/src/ts/components/common/DiscordAvatar.tsx b/frontend/src/ts/components/common/DiscordAvatar.tsx
index 3edd40c926a6..e1ae76231234 100644
--- a/frontend/src/ts/components/common/DiscordAvatar.tsx
+++ b/frontend/src/ts/components/common/DiscordAvatar.tsx
@@ -13,6 +13,7 @@ export function DiscordAvatar(props: {
discordAvatar: string | undefined;
size?: number;
class?: string;
+ fallbackIcon?: "user-circle" | "user";
}): JSXElement {
const cacheKey = (): string => `${props.discordId}/${props.discordAvatar}`;
const [showSpinner, setShowSpinner] = createSignal(true);
@@ -22,6 +23,35 @@ export function DiscordAvatar(props: {
props.discordAvatar !== undefined &&
avatar[cacheKey()] !== false;
+ const fallback = () => {
+ if (
+ props.fallbackIcon === "user-circle" ||
+ props.fallbackIcon === undefined
+ ) {
+ return (
+
+ );
+ } else {
+ return (
+
+ );
+ }
+ };
+
return (
>
}
- else={
-
- }
+ else={fallback()}
/>
);
diff --git a/frontend/src/ts/components/common/NotificationBubble.tsx b/frontend/src/ts/components/common/NotificationBubble.tsx
new file mode 100644
index 000000000000..49b3f9b3c5f8
--- /dev/null
+++ b/frontend/src/ts/components/common/NotificationBubble.tsx
@@ -0,0 +1,27 @@
+import { JSXElement, Show } from "solid-js";
+
+import { cn } from "../../utils/cn";
+
+type Props = {
+ variant: "fromCorner" | "atCorner" | "center";
+ show: boolean;
+ class?: string;
+};
+
+export function NotificationBubble(props: Props): JSXElement {
+ return (
+