From 0a5af810c3543dcc92d177a1e3a4e8e55bf3fe98 Mon Sep 17 00:00:00 2001 From: Jasper Lai <33956381+jaskfla@users.noreply.github.com> Date: Wed, 6 May 2026 19:36:37 +1200 Subject: [PATCH 1/9] redesign --- .../src/components/ContentUnavailableView.tsx | 33 ++++ .../src/components/button/PrimitiveButton.tsx | 1 + .../src/components/canvas/CanvasView.tsx | 2 - .../frontend/src/components/header/Nav.tsx | 12 +- .../src/components/notices/NoticeBanner.tsx | 169 ------------------ .../src/components/notices/NoticeList.tsx | 57 ++++++ .../src/components/notices/NoticeListItem.tsx | 101 +++++++++++ .../src/components/notices/Noticeboard.tsx | 84 +++++++++ .../src/components/notices/Notices.tsx | 87 --------- .../frontend/src/components/notices/index.ts | 1 + packages/frontend/src/styles/core.css | 11 ++ 11 files changed, 290 insertions(+), 268 deletions(-) create mode 100644 packages/frontend/src/components/ContentUnavailableView.tsx delete mode 100644 packages/frontend/src/components/notices/NoticeBanner.tsx create mode 100644 packages/frontend/src/components/notices/NoticeList.tsx create mode 100644 packages/frontend/src/components/notices/NoticeListItem.tsx create mode 100644 packages/frontend/src/components/notices/Noticeboard.tsx delete mode 100644 packages/frontend/src/components/notices/Notices.tsx create mode 100644 packages/frontend/src/components/notices/index.ts diff --git a/packages/frontend/src/components/ContentUnavailableView.tsx b/packages/frontend/src/components/ContentUnavailableView.tsx new file mode 100644 index 000000000..6d4a429bf --- /dev/null +++ b/packages/frontend/src/components/ContentUnavailableView.tsx @@ -0,0 +1,33 @@ +import { styled } from "@mui/material"; + +export const Root = styled("article")` + align-content: center; + margin-trim: block; + min-block-size: min(100vb, 16em); /* fallback */ + min-block-size: min(100dvb, 16em); + padding-block: 1.5rem; + text-align: center; + text-wrap: balance; + svg, + .lucide { + margin-inline: auto; + opacity: 55%; + font-size: 3.375rem; + height: 3.375rem; + width: auto; + } +`; + +export const Heading = styled("h2")` + color: unset; + font-size: 1.5em; + font-weight: 600; + margin-block-start: 0.5em; +`; + +export const Description = styled("p")` + color: ${(props) => props.theme.palette.text.secondary}; + margin-block-start: 0.5em; +`; + +export default { Root, Heading, Description }; diff --git a/packages/frontend/src/components/button/PrimitiveButton.tsx b/packages/frontend/src/components/button/PrimitiveButton.tsx index 80da682d8..26c3d3c12 100644 --- a/packages/frontend/src/components/button/PrimitiveButton.tsx +++ b/packages/frontend/src/components/button/PrimitiveButton.tsx @@ -2,6 +2,7 @@ import { styled } from "@mui/material"; const PrimitiveButton = styled("button")` appearance: none; + background-color: initial; border: none; color: inherit; font-family: inherit; diff --git a/packages/frontend/src/components/canvas/CanvasView.tsx b/packages/frontend/src/components/canvas/CanvasView.tsx index 099691101..3cf2459d9 100644 --- a/packages/frontend/src/components/canvas/CanvasView.tsx +++ b/packages/frontend/src/components/canvas/CanvasView.tsx @@ -206,7 +206,6 @@ const FullscreenButton = styled(BaseFullscreenButton)` const FullscreenPanelButton = styled(BaseFullscreenButton)` border-radius: 0.5rem 0.5rem 0.5rem 1rem; inset-block-start: 4rem; - z-index: 3; `; const FullscreenPanelOverlay = styled("div")` @@ -218,7 +217,6 @@ const FullscreenPanelOverlay = styled("div")` pointer-events: auto; position: absolute; width: min(var(--action-panel-width), calc(100vw - 1rem)); - z-index: 2; > * { width: 100%; diff --git a/packages/frontend/src/components/header/Nav.tsx b/packages/frontend/src/components/header/Nav.tsx index 945927470..5c1949d57 100644 --- a/packages/frontend/src/components/header/Nav.tsx +++ b/packages/frontend/src/components/header/Nav.tsx @@ -9,25 +9,16 @@ import Link from "next/link"; import type React from "react"; import { useState } from "react"; import { useAuthContext } from "@/contexts"; +import { Noticeboard } from "../notices"; const Links = styled("ul")` display: none; - ${({ theme }) => theme.breakpoints.up("md")} { display: flex; } - li { display: inline-flex; } - - /* - * Workaround for accessibility issue with VoiceOver. - * See https://gerardkcohen.me/writing/2017/voiceover-list-style-type.html - */ - li::before { - content: "\\200B"; /* zero-width space */ - } `; export const NavLink = styled(Link)` @@ -129,6 +120,7 @@ export default function Nav() { ))} + {links.map(({ href, label }) => (
  • {label} diff --git a/packages/frontend/src/components/notices/NoticeBanner.tsx b/packages/frontend/src/components/notices/NoticeBanner.tsx deleted file mode 100644 index a020e3bd5..000000000 --- a/packages/frontend/src/components/notices/NoticeBanner.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import type { Notice } from "@blurple-canvas-web/types"; -import { styled } from "@mui/material/styles"; -import { CircleAlert, Info, TriangleAlert } from "lucide-react"; -import Markdown from "markdown-to-jsx"; -import { resolveSpecialText } from "@/util/text"; - -type BannerComponent = typeof StyledBanner; - -const StyledBanner = styled("div")` - align-items: center; - background-color: var(--discord-legacy-dark-but-not-black); - border-radius: var(--card-border-radius); - border: 3px solid; - box-shadow: 0 0 10px rgba(0 0 0 / 50%); - cursor: default; - display: flex; - flex-direction: row; - gap: 1rem; - justify-content: center; - padding: 1rem; - width: fit-content; - - & > svg { - flex: 0 0 auto; - width: 1.25rem; - height: 1.25rem; - } -`; - -const StyledInfoBanner = styled(StyledBanner)` - border-color: oklch(from var(--discord-white) l c h / 50%); -`; - -const StyledWarningBanner = styled(StyledBanner)` - border-color: var(--discord-blurple); -`; - -const StyledErrorBanner = styled(StyledBanner)` - border-color: var(--discord-red); -`; - -const BannerBody = styled("div")` - display: flex; - flex-direction: column; - gap: 0.25rem; - min-width: 0; -`; - -const ContentSpan = styled("span")` - > div { - display: flex; - flex-direction: column; - } -`; - -const DismissWrapper = styled("div")` - display: flex; - flex-direction: row; - gap: 1rem; -`; - -const DismissButton = styled("button")` - background: transparent; - border: none; - cursor: pointer; - flex: 0 0 auto; - opacity: 50%; - transition: opacity var(--transition-duration-fast) ease; - - :hover { - opacity: 80%; - } -`; - -interface BannerProps { - notice: Notice; - onDismiss?: () => void; - onDismissPermanently?: () => void; -} - -function Banner({ - notice, - BannerRoot, - icon, - onDismiss, - onDismissPermanently, -}: { - BannerRoot: BannerComponent; - icon: React.ReactNode; -} & BannerProps) { - const headerText = - notice.header ? `### ${resolveSpecialText(notice.header)}` : ""; - const contentText = notice.content ? resolveSpecialText(notice.content) : ""; - - return ( - { - e.stopPropagation(); - }} - > - {icon} - - {headerText && ( - - {headerText} - - )} - {contentText && ( - - {contentText} - - )} - - Dismiss - {!notice.persisted && ( - - Don't show again - - )} - - - - ); -} - -export default function NoticeBanner({ - notice, - onDismiss, - onDismissPermanently, -}: { - notice: Notice; - onDismiss?: () => void; - onDismissPermanently?: () => void; -}) { - switch (notice.type) { - case "info": - return ( - } - notice={notice} - onDismiss={onDismiss} - onDismissPermanently={onDismissPermanently} - /> - ); - case "warning": - return ( - } - notice={notice} - onDismiss={onDismiss} - onDismissPermanently={onDismissPermanently} - /> - ); - case "error": - return ( - } - notice={notice} - onDismiss={onDismiss} - onDismissPermanently={onDismissPermanently} - /> - ); - default: - return null; - } -} diff --git a/packages/frontend/src/components/notices/NoticeList.tsx b/packages/frontend/src/components/notices/NoticeList.tsx new file mode 100644 index 000000000..9c6e9b6f0 --- /dev/null +++ b/packages/frontend/src/components/notices/NoticeList.tsx @@ -0,0 +1,57 @@ +"use client"; + +import { styled } from "@mui/material"; +import { MegaphoneOff } from "lucide-react"; +import { useCanvasContext } from "@/contexts"; +import { useNotices } from "@/hooks/queries/useNotice"; +import ContentUnavailableView from "../ContentUnavailableView"; +import NoticeListItem from "./NoticeListItem"; + +const UnorderedList = styled("ul")` + align-content: flex-start; + align-items: center; + display: grid; + font-size: 1rem; + gap: 0.75em; + grid-template-columns: auto 1fr; + line-height: 1.55; +`; + +export default function NoticeList( + props: React.ComponentPropsWithRef, +) { + const { data: notices } = useNotices(); + const { canvas } = useCanvasContext(); + + const filtered = + notices + ?.filter( + (notice) => notice.canvasId === null || notice.canvasId === canvas?.id, + ) + .sort( + (a, b) => + a.priority - b.priority || + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), + ) ?? []; + + if (filtered.length === 0) { + return ( + + + + No notices + + + No notices for {canvas.name} + + + ); + } + return ( + + {filtered.map((notice) => ( + + ))} + + ); +} diff --git a/packages/frontend/src/components/notices/NoticeListItem.tsx b/packages/frontend/src/components/notices/NoticeListItem.tsx new file mode 100644 index 000000000..a370771e9 --- /dev/null +++ b/packages/frontend/src/components/notices/NoticeListItem.tsx @@ -0,0 +1,101 @@ +import type { Notice } from "@blurple-canvas-web/types"; +import { styled } from "@mui/material/styles"; +import { CircleAlert, Info, TriangleAlert } from "lucide-react"; +import Markdown from "markdown-to-jsx"; +import { resolveSpecialText } from "@/util/text"; + +const icons = { + info: , + error: , + warning: , +} as const; + +const IconWrapper = styled("div")` + background-color: oklch(from var(--notice-tint) l c h / 15%); + border-radius: calc(infinity * 1px); + display: grid; + place-items: center; + width: 2rem; + height: 2rem; +`; + +const BannerRoot = styled("li")` + --notice-tint: var(--discord-white;); + background-color: oklch(from var(--notice-tint) l c h / 6%); + border-radius: 0.75rem; + border: var(--card-border); + display: grid; + gap: inherit; + grid-column: 1 / -1; + grid-template-columns: subgrid; + padding: 0.75rem; + font-weight: 450; + letter-spacing: 0.01em; + + @supports (color: color-mix(in oklab, black, black)) { + color: color-mix(in oklab, currentColor 90%, var(--notice-tint)); + } + + &[data-severity="warning"] { + background-color: transparent; + } + &[data-severity="warning"] { + --notice-tint: var(--discord-blurple); + } + &[data-severity="error"] { + --notice-tint: var(--discord-red); + } + + ${IconWrapper} { + opacity: 94%; + color: var(--notice-tint); + } +`; + +const BannerBody = styled("div")` + * + * { + margin-block-start: 0.5em; + } + h1, + h2, + h3, + h4, + h5, + h6 { + font-size: inherit; + font-weight: bolder; + } +`; + +interface NoticeListItemProps extends React.ComponentPropsWithRef< + typeof BannerRoot +> { + notice: Notice; +} + +export default function NoticeListItem({ + notice, + ...props +}: NoticeListItemProps) { + const headerText = + notice.header ? `### ${resolveSpecialText(notice.header)}` : ""; + const contentText = notice.content ? resolveSpecialText(notice.content) : ""; + + return ( + e.stopPropagation()} + {...props} + > + {icons[notice.type]} + + {headerText && ( +
    + {headerText} +
    + )} + {contentText && {contentText}} +
    +
    + ); +} diff --git a/packages/frontend/src/components/notices/Noticeboard.tsx b/packages/frontend/src/components/notices/Noticeboard.tsx new file mode 100644 index 000000000..2c0c8f209 --- /dev/null +++ b/packages/frontend/src/components/notices/Noticeboard.tsx @@ -0,0 +1,84 @@ +"use client"; + +import { IconButton, styled } from "@mui/material"; +import { Megaphone } from "lucide-react"; +import { useId, useRef } from "react"; +import { createPortal } from "react-dom"; +import VisuallyHidden from "../VisuallyHidden"; +import NoticeList from "./NoticeList"; + +const anchorName = "--notices-button"; + +const StyledIconButton = styled(IconButton)` + anchor-name: ${anchorName}; +`; + +const Dialog = styled("dialog")` + background-color: var(--discord-legacy-dark-but-not-black); + border-radius: var(--card-border-radius); + border: var(--card-border); + box-shadow: 0 0 10px oklch(0 0 0 / 25%); + inline-size: min(100vi, 36rem); /* fallback */ + inline-size: min(100dvi, 36rem); + max-block-size: min(90vh, 60rem); /* fallback */ + max-block-size: min(90dvh, 60rem); + overflow: auto; + position-anchor: ${anchorName}; + position-area: block-end center; + position: absolute; + transform-origin: top center; + transition-duration: 100ms; + transition-property: opacity, transform; + transition-timing-function: linear, var(--ease-out-quart); + z-index: 1; /* Fighting with here 😔 */ + + > * { + padding: calc(var(--card-border-radius) - 0.75rem); + } + + opacity: 1; + transform: scale(100%); + @starting-style { + opacity: 0; + transform: scale(97%); + } +`; + +const Header = styled("header")` + align-items: center; + border-block-end: var(--card-border); + display: flex; + position: sticky; +`; + +const Heading = styled("h2")` + color: unset; + font-size: inherit; + font-weight: 500; + margin-inline: 0.75rem; +`; + +export default function Noticeboard() { + const buttonId = useId(); + const buttonRef = useRef(null); + const dialogId = useId(); + const dialogRef = useRef(null); + + return ( + <> + + + Notices + + {createPortal( + +
    + Notices +
    + +
    , + document.body, + )} + + ); +} diff --git a/packages/frontend/src/components/notices/Notices.tsx b/packages/frontend/src/components/notices/Notices.tsx deleted file mode 100644 index 20e52080f..000000000 --- a/packages/frontend/src/components/notices/Notices.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { styled } from "@mui/material"; -import { useCallback, useMemo, useState } from "react"; -import useLocalStorage from "@/app/settings/useLocalStorage"; -import { useCanvasContext } from "@/contexts"; -import { useNotices } from "@/hooks/queries/useNotice"; -import NoticeBanner from "./NoticeBanner"; - -const NoticeWrapper = styled("div")` - align-items: center; - display: flex; - flex-direction: column; - gap: 0.5rem; - left: 50%; - margin-top: 2.5rem; - pointer-events: auto; - position: absolute; - top: 0; - transform: translateX(-50%); - width: 90%; - z-index: 2000; -`; - -export default function Notices() { - const { data: notices = [] } = useNotices(); - const { canvas } = useCanvasContext(); - - const [persistedDismissed = [], setPersistedDismissed] = - useLocalStorage("notices/dismissed"); - - const [transientDismissed, setTransientDismissed] = useState>( - new Set(), - ); - - const persistedSet = useMemo( - () => new Set(persistedDismissed), - [persistedDismissed], - ); - - const dismiss = useCallback( - (id: string, persist: boolean = false) => { - setTransientDismissed((s) => { - const next = new Set(s); - next.add(id); - return next; - }); - - const n = notices.find((x) => x.id === Number.parseInt(id, 10)); - if (persist && n && n.persisted === false) { - const nextArr = Array.from( - new Set([...(persistedDismissed ?? []), id]), - ); - setPersistedDismissed(nextArr); - } - }, - [notices, persistedDismissed, setPersistedDismissed], - ); - - const filteredNotices = notices - .filter( - (notice) => - (notice.canvasId === null || notice.canvasId === canvas?.id) && - !transientDismissed.has(notice.id.toString()) && - !persistedSet.has(notice.id.toString()), - ) - .sort((a, b) => { - if (a.priority !== b.priority) { - return a.priority - b.priority; - } else { - return ( - new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() - ); - } - }); - - return ( - - {filteredNotices.map((notice) => ( - dismiss(notice.id.toString(), false)} - onDismissPermanently={() => dismiss(notice.id.toString(), true)} - /> - ))} - - ); -} diff --git a/packages/frontend/src/components/notices/index.ts b/packages/frontend/src/components/notices/index.ts new file mode 100644 index 000000000..3697b737c --- /dev/null +++ b/packages/frontend/src/components/notices/index.ts @@ -0,0 +1 @@ +export { default as Noticeboard } from "./Noticeboard"; diff --git a/packages/frontend/src/styles/core.css b/packages/frontend/src/styles/core.css index 6598b2fe4..14f3aca94 100644 --- a/packages/frontend/src/styles/core.css +++ b/packages/frontend/src/styles/core.css @@ -87,6 +87,10 @@ text-decoration: none; } + dialog { + border: 0; + } + hr { box-sizing: content-box; height: 0; @@ -140,6 +144,13 @@ :is(ol, ul)[role="list"] { list-style-type: none; + /* + * Workaround for accessibility issue with VoiceOver. + * See https://gerardkcohen.me/writing/2017/voiceover-list-style-type.html + */ + li::before { + content: "\200B"; /* zero-width space */ + } } table { From 8029ed4f4dda1dc7bfc595afb9a1811235270d38 Mon Sep 17 00:00:00 2001 From: Jasper Lai <33956381+jaskfla@users.noreply.github.com> Date: Wed, 13 May 2026 17:34:33 +1200 Subject: [PATCH 2/9] spacing tweaks --- packages/frontend/src/components/header/Nav.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/frontend/src/components/header/Nav.tsx b/packages/frontend/src/components/header/Nav.tsx index 5c1949d57..78bc5fc2b 100644 --- a/packages/frontend/src/components/header/Nav.tsx +++ b/packages/frontend/src/components/header/Nav.tsx @@ -14,6 +14,7 @@ import { Noticeboard } from "../notices"; const Links = styled("ul")` display: none; ${({ theme }) => theme.breakpoints.up("md")} { + align-items: baseline; display: flex; } li { @@ -24,12 +25,11 @@ const Links = styled("ul")` export const NavLink = styled(Link)` border-radius: 0.5rem; color: var(--discord-white); - padding: 0.5rem 1rem; + padding-inline: 0.66666667em; + padding-block: 0.25em; text-decoration: none; - transition: - background-color var(--transition-duration-fast) ease, - opacity var(--transition-duration-fast) ease, - outline-width var(--transition-duration-fast) ease; + transition: var(--transition-duration-fast) ease; + transition-property: background-color, outline-width; :hover { opacity: 55%; From 91ef898a600f46d62716822e7632152658693b16 Mon Sep 17 00:00:00 2001 From: S-Ou Date: Mon, 18 May 2026 20:55:16 +1200 Subject: [PATCH 3/9] fix: build --- packages/frontend/src/components/canvas/CanvasView.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/frontend/src/components/canvas/CanvasView.tsx b/packages/frontend/src/components/canvas/CanvasView.tsx index 3cf2459d9..90e49247b 100644 --- a/packages/frontend/src/components/canvas/CanvasView.tsx +++ b/packages/frontend/src/components/canvas/CanvasView.tsx @@ -36,7 +36,6 @@ import { CANVAS_WRAPPER_CLASS_NAME, clamp, normalizeFrameBounds } from "@/util"; import type { ActionPanel } from "../action-panel"; import { Button } from "../button"; import CanvasAnimatedIcon from "../CanvasAnimatedIcon"; -import Notices from "../notices/Notices"; import VisuallyHidden from "../VisuallyHidden"; import { addPoints, @@ -483,7 +482,6 @@ interface CanvasViewProps { >; canvasLabel?: string; showInvite?: boolean; - showNotices?: boolean; showReticle?: boolean; } @@ -491,7 +489,6 @@ export default function CanvasView({ actionPanel, canvasLabel, showInvite = true, - showNotices = true, showReticle = true, }: CanvasViewProps) { const imageRef = useRef(null); @@ -1177,7 +1174,6 @@ export default function CanvasView({ ref={containerRef} onPointerDown={handlePointerDown} > - {showNotices && } {canUseFullscreen && ( Date: Mon, 18 May 2026 20:59:09 +1200 Subject: [PATCH 4/9] fix: actually fix build --- packages/frontend/src/app/moderation/ModerationDashboard.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/frontend/src/app/moderation/ModerationDashboard.tsx b/packages/frontend/src/app/moderation/ModerationDashboard.tsx index 4fbf461ad..653d27c53 100644 --- a/packages/frontend/src/app/moderation/ModerationDashboard.tsx +++ b/packages/frontend/src/app/moderation/ModerationDashboard.tsx @@ -29,7 +29,6 @@ export default function ModerationDashboard() { actionPanel={} canvasLabel="Moderation dashboard" showInvite={false} - showNotices={false} showReticle={false} /> From 9798203952b2fc11ff1cb5510da2d438be018e5d Mon Sep 17 00:00:00 2001 From: Jasper Lai <33956381+jaskfla@users.noreply.github.com> Date: Wed, 20 May 2026 16:08:29 +1200 Subject: [PATCH 5/9] fixes --- packages/frontend/src/components/header/Nav.tsx | 2 ++ .../frontend/src/components/notices/NoticeListItem.tsx | 9 +++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/frontend/src/components/header/Nav.tsx b/packages/frontend/src/components/header/Nav.tsx index 0112ed450..b7ed3a941 100644 --- a/packages/frontend/src/components/header/Nav.tsx +++ b/packages/frontend/src/components/header/Nav.tsx @@ -25,6 +25,8 @@ const Links = styled("ul")` export const NavLink = styled(Link)` border-radius: 0.5rem; color: var(--discord-white); + font-weight: 450; + letter-spacing: 0.01em; padding-block: 0.5rem; padding-inline: 0.75rem; text-decoration: none; diff --git a/packages/frontend/src/components/notices/NoticeListItem.tsx b/packages/frontend/src/components/notices/NoticeListItem.tsx index a370771e9..7d42ea958 100644 --- a/packages/frontend/src/components/notices/NoticeListItem.tsx +++ b/packages/frontend/src/components/notices/NoticeListItem.tsx @@ -13,10 +13,12 @@ const icons = { const IconWrapper = styled("div")` background-color: oklch(from var(--notice-tint) l c h / 15%); border-radius: calc(infinity * 1px); + color: var(--notice-tint); display: grid; + height: 2rem; + opacity: 94%; place-items: center; width: 2rem; - height: 2rem; `; const BannerRoot = styled("li")` @@ -45,11 +47,6 @@ const BannerRoot = styled("li")` &[data-severity="error"] { --notice-tint: var(--discord-red); } - - ${IconWrapper} { - opacity: 94%; - color: var(--notice-tint); - } `; const BannerBody = styled("div")` From 9329b26345d5fdf3d46185bbbf6172d5dd1f3d76 Mon Sep 17 00:00:00 2001 From: Jasper Lai <33956381+jaskfla@users.noreply.github.com> Date: Wed, 20 May 2026 16:19:19 +1200 Subject: [PATCH 6/9] fix --- .../frontend/src/components/notices/NoticeListItem.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/frontend/src/components/notices/NoticeListItem.tsx b/packages/frontend/src/components/notices/NoticeListItem.tsx index 7d42ea958..10268d271 100644 --- a/packages/frontend/src/components/notices/NoticeListItem.tsx +++ b/packages/frontend/src/components/notices/NoticeListItem.tsx @@ -47,6 +47,14 @@ const BannerRoot = styled("li")` &[data-severity="error"] { --notice-tint: var(--discord-red); } + + /* + * Pseudoelement disrupts the grid flow. Notices not being declared as a list in WebKit not end of + * world. Sorta newsfeedy semantics anyway. + */ + &::before { + content: unset; + } `; const BannerBody = styled("div")` From cb4427c2c28850510f26b577f4ac65ede0922656 Mon Sep 17 00:00:00 2001 From: Jasper Lai <33956381+jaskfla@users.noreply.github.com> Date: Mon, 1 Jun 2026 14:06:26 +1200 Subject: [PATCH 7/9] fix --- .../src/components/notices/Noticeboard.tsx | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/frontend/src/components/notices/Noticeboard.tsx b/packages/frontend/src/components/notices/Noticeboard.tsx index 2c0c8f209..f6436a56b 100644 --- a/packages/frontend/src/components/notices/Noticeboard.tsx +++ b/packages/frontend/src/components/notices/Noticeboard.tsx @@ -2,7 +2,7 @@ import { IconButton, styled } from "@mui/material"; import { Megaphone } from "lucide-react"; -import { useId, useRef } from "react"; +import { useEffect, useId, useRef, useState } from "react"; import { createPortal } from "react-dom"; import VisuallyHidden from "../VisuallyHidden"; import NoticeList from "./NoticeList"; @@ -59,6 +59,9 @@ const Heading = styled("h2")` `; export default function Noticeboard() { + const [isMounted, setIsMounted] = useState(false); + useEffect(() => setIsMounted(true), []); + const buttonId = useId(); const buttonRef = useRef(null); const dialogId = useId(); @@ -70,15 +73,16 @@ export default function Noticeboard() { Notices - {createPortal( - -
    - Notices -
    - -
    , - document.body, - )} + {isMounted && + createPortal( + +
    + Notices +
    + +
    , + document.body, + )} ); } From 54063a626b2fdb58c6caab962544d2c2d9c01f0f Mon Sep 17 00:00:00 2001 From: Jasper Lai <33956381+jaskfla@users.noreply.github.com> Date: Mon, 1 Jun 2026 14:17:10 +1200 Subject: [PATCH 8/9] tweak --- packages/frontend/src/components/ContentUnavailableView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/components/ContentUnavailableView.tsx b/packages/frontend/src/components/ContentUnavailableView.tsx index 6d4a429bf..2edd1b0b4 100644 --- a/packages/frontend/src/components/ContentUnavailableView.tsx +++ b/packages/frontend/src/components/ContentUnavailableView.tsx @@ -5,7 +5,7 @@ export const Root = styled("article")` margin-trim: block; min-block-size: min(100vb, 16em); /* fallback */ min-block-size: min(100dvb, 16em); - padding-block: 1.5rem; + padding-block: 2rem; text-align: center; text-wrap: balance; svg, From 49ed52125ba37b47d2aab559332287f6b56cfc20 Mon Sep 17 00:00:00 2001 From: Jasper Lai <33956381+jaskfla@users.noreply.github.com> Date: Mon, 1 Jun 2026 14:19:05 +1200 Subject: [PATCH 9/9] fix build --- packages/frontend/src/app/canvas/[canvasId]/admin/paste/page.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/frontend/src/app/canvas/[canvasId]/admin/paste/page.tsx b/packages/frontend/src/app/canvas/[canvasId]/admin/paste/page.tsx index 7ea55638b..81fb80708 100644 --- a/packages/frontend/src/app/canvas/[canvasId]/admin/paste/page.tsx +++ b/packages/frontend/src/app/canvas/[canvasId]/admin/paste/page.tsx @@ -450,7 +450,6 @@ function AdminPasteTab() { actionPanel={actionPanel} canvasLabel="Admin paste" showInvite={false} - showNotices={false} showReticle={false} /> {actionPanel}