diff --git a/docs/assets/block-style-refined.webp b/docs/assets/block-style-refined.webp new file mode 100644 index 000000000..d2cc236e5 Binary files /dev/null and b/docs/assets/block-style-refined.webp differ diff --git a/docs/assets/card-style-hairline.webp b/docs/assets/card-style-hairline.webp new file mode 100644 index 000000000..113722dc3 Binary files /dev/null and b/docs/assets/card-style-hairline.webp differ diff --git a/docs/assets/font-plex-data.webp b/docs/assets/font-plex-data.webp new file mode 100644 index 000000000..dfd2f82f0 Binary files /dev/null and b/docs/assets/font-plex-data.webp differ diff --git a/docs/assets/font-plex.webp b/docs/assets/font-plex.webp new file mode 100644 index 000000000..a544b50fd Binary files /dev/null and b/docs/assets/font-plex.webp differ diff --git a/docs/assets/style-default.webp b/docs/assets/style-default.webp new file mode 100644 index 000000000..c647dc3ec Binary files /dev/null and b/docs/assets/style-default.webp differ diff --git a/docs/configs/settings.md b/docs/configs/settings.md index 2b8e09b9b..010c91875 100644 --- a/docs/configs/settings.md +++ b/docs/configs/settings.md @@ -430,6 +430,64 @@ headerStyle: clean headerStyle: boxedWidgets ``` +## Card Style + +Service and bookmark cards use a drop shadow by default. The `hairline` style replaces it with a thin border that brightens on hover. + +default cards + +```yaml +# default, shadowed cards +``` + +--- + +hairline cards + +```yaml +cardStyle: hairline +``` + +## Block Style + +Widget blocks (the value / label pairs inside a service card) can use the `refined` style to make values heavier and easier to read. + +default blocks + +```yaml +# default, standard blocks +``` + +--- + +refined blocks + +```yaml +blockStyle: refined +``` + +## Font + +Homepage uses Manrope by default. You can switch to IBM Plex Mono instead, either everywhere or for data only. + +IBM Plex Mono everywhere + +```yaml +font: plex # the whole dashboard +``` + +--- + +IBM Plex Mono for data only + +```yaml +font: plexData # only values, stats, the clock and status tags +``` + +With `plexData`, labels and names stay in Manrope while numbers line up in columns. + +Both fonts are bundled with Homepage, so no external requests are made. + ## Base URL In some proxy configurations, it may be necessary to set the documents base URL. You can do this by providing a `base` value, like so: diff --git a/src/components/bookmarks/item.jsx b/src/components/bookmarks/item.jsx index 6d2c427bc..51479f2a6 100644 --- a/src/components/bookmarks/item.jsx +++ b/src/components/bookmarks/item.jsx @@ -7,6 +7,10 @@ import { SettingsContext } from "utils/contexts/settings"; export default function Item({ bookmark, iconOnly = false }) { const description = bookmark.description ?? new URL(bookmark.href).hostname; const { settings } = useContext(SettingsContext); + const cardStyle = + settings.cardStyle === "hairline" + ? "shadow-xs inset-ring inset-ring-black/5 dark:inset-ring-white/5 hover:inset-ring-black/10 dark:hover:inset-ring-white/10" + : "shadow-md shadow-theme-900/10 dark:shadow-theme-900/20"; return (
  • diff --git a/src/components/bookmarks/item.test.jsx b/src/components/bookmarks/item.test.jsx index 2309bbc18..945f3c6e8 100644 --- a/src/components/bookmarks/item.test.jsx +++ b/src/components/bookmarks/item.test.jsx @@ -21,6 +21,16 @@ describe("components/bookmarks/item", () => { expect(screen.getByRole("link").getAttribute("target")).toBe("_self"); }); + it("applies hairline card styles when cardStyle is set", () => { + renderWithProviders(, { + settings: { cardStyle: "hairline" }, + }); + + const className = screen.getByRole("link").className; + expect(className).toContain("inset-ring"); + expect(className).not.toContain("shadow-md"); + }); + it("renders icon-only layout with icon when provided", () => { renderWithProviders( , diff --git a/src/components/services/item.jsx b/src/components/services/item.jsx index 88c6383ae..05757fddf 100644 --- a/src/components/services/item.jsx +++ b/src/components/services/item.jsx @@ -19,6 +19,10 @@ export default function Item({ service, groupName, useEqualHeights }) { const { settings } = useContext(SettingsContext); const showStats = service.showStats === false ? false : settings.showStats; const statusStyle = service.statusStyle !== undefined ? service.statusStyle : settings.statusStyle; + const cardStyle = + settings.cardStyle === "hairline" + ? "shadow-xs inset-ring inset-ring-black/5 dark:inset-ring-white/5 hover:inset-ring-black/10 dark:hover:inset-ring-white/10" + : "shadow-md shadow-theme-900/10 dark:shadow-theme-900/20"; const [statsOpen, setStatsOpen] = useState(service.showStats); const [statsClosing, setStatsClosing] = useState(false); @@ -39,7 +43,8 @@ export default function Item({ service, groupName, useEqualHeights }) { className={classNames( settings.cardBlur !== undefined && `backdrop-blur${settings.cardBlur.length ? "-" : ""}${settings.cardBlur}`, useEqualHeights && "h-[calc(100%-0.5rem)]", - "transition-all mb-2 p-1 rounded-md font-medium text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/20 bg-theme-100/20 hover:bg-theme-300/20 dark:bg-white/5 dark:hover:bg-white/10 relative overflow-clip service-card", + cardStyle, + "transition-all mb-2 p-1 rounded-md font-medium text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 bg-theme-100/20 hover:bg-theme-300/20 dark:bg-white/5 dark:hover:bg-white/10 relative overflow-clip service-card", )} >
    diff --git a/src/components/services/widget/block.jsx b/src/components/services/widget/block.jsx index a7dbda19f..078dd6d84 100644 --- a/src/components/services/widget/block.jsx +++ b/src/components/services/widget/block.jsx @@ -4,11 +4,14 @@ import { useContext, useMemo } from "react"; import { BlockHighlightContext } from "./highlight-context"; +import { SettingsContext } from "utils/contexts/settings"; import { evaluateHighlight, getHighlightClass } from "utils/highlights"; export default function Block({ value, highlightValue, label, field }) { const { t } = useTranslation(); const highlightConfig = useContext(BlockHighlightContext); + const { settings } = useContext(SettingsContext); + const refined = settings?.blockStyle === "refined"; const highlight = useMemo(() => { if (!highlightConfig) return null; @@ -45,9 +48,20 @@ export default function Block({ value, highlightValue, label, field }) { data-highlight-level={highlight?.level} data-highlight-source={highlight?.source} > -
    {value === undefined || value === null ? "-" : value}
    + {value === undefined || value === null ? "-" : value} +
    +
    {t(label)}
    diff --git a/src/components/services/widget/block.test.jsx b/src/components/services/widget/block.test.jsx index 38ee0d137..fdd0ace41 100644 --- a/src/components/services/widget/block.test.jsx +++ b/src/components/services/widget/block.test.jsx @@ -39,6 +39,24 @@ describe("components/services/widget/block", () => { expect(el.className).toContain("danger-class"); }); + it("uses the standard block styles by default", () => { + const { container } = renderWithProviders(, { settings: {} }); + + expect(container.querySelector(".service-block-value").className).toContain("font-thin"); + expect(container.querySelector(".service-block-label").className).toContain("font-bold"); + }); + + it("applies refined block styles when blockStyle is set", () => { + const { container } = renderWithProviders(, { + settings: { blockStyle: "refined" }, + }); + + const value = container.querySelector(".service-block-value"); + expect(value.className).toContain("tabular-nums"); + expect(value.className).not.toContain("font-thin"); + expect(container.querySelector(".service-block-label").className).toContain("tracking-wider"); + }); + it("prefers highlightValue over the rendered value for numeric highlighting", () => { const highlightConfig = { levels: { warn: "warn-class" }, diff --git a/src/components/widgets/widget/resource.jsx b/src/components/widgets/widget/resource.jsx index e80324b21..249fd3960 100644 --- a/src/components/widgets/widget/resource.jsx +++ b/src/components/widgets/widget/resource.jsx @@ -25,12 +25,12 @@ export default function Resource({ }`} >
    -
    {value}
    +
    {value}
    {label}
    {expanded && (
    -
    {expandedValue}
    +
    {expandedValue}
    {expandedLabel}
    )} diff --git a/src/pages/_app.jsx b/src/pages/_app.jsx index 7c7216660..14ce3b869 100644 --- a/src/pages/_app.jsx +++ b/src/pages/_app.jsx @@ -3,6 +3,7 @@ import { appWithTranslation } from "next-i18next/pages"; import Head from "next/head"; import "styles/globals.css"; import "styles/manrope.css"; +import "styles/plex-mono.css"; import "styles/theme.css"; import { SWRConfig } from "swr"; diff --git a/src/pages/index.jsx b/src/pages/index.jsx index 31b095bf1..304e2ffa5 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -552,7 +552,9 @@ export default function Wrapper({ initialSettings, fallback }) { const html = document.documentElement; const body = document.body; - html.classList.remove("dark", "scheme-dark", "scheme-light"); + html.classList.remove("dark", "scheme-dark", "scheme-light", "font-plex", "font-plex-data"); + if (initialSettings.font === "plex") html.classList.add("font-plex"); + if (initialSettings.font === "plexData") html.classList.add("font-plex-data"); html.classList.toggle("dark", theme === "dark"); html.classList.add(theme === "dark" ? "scheme-dark" : "scheme-light"); @@ -571,7 +573,7 @@ export default function Wrapper({ initialSettings, fallback }) { body.style.backgroundImage = ""; body.style.backgroundColor = ""; body.style.backgroundAttachment = ""; - }, [backgroundImage, opacity, theme, color, initialSettings.color]); + }, [backgroundImage, opacity, theme, color, initialSettings.color, initialSettings.font]); return ( <> diff --git a/src/styles/font/IBMPlexMono-Bold.woff2 b/src/styles/font/IBMPlexMono-Bold.woff2 new file mode 100644 index 000000000..6200c4928 Binary files /dev/null and b/src/styles/font/IBMPlexMono-Bold.woff2 differ diff --git a/src/styles/font/IBMPlexMono-LICENSE.txt b/src/styles/font/IBMPlexMono-LICENSE.txt new file mode 100644 index 000000000..f465e97b9 --- /dev/null +++ b/src/styles/font/IBMPlexMono-LICENSE.txt @@ -0,0 +1,93 @@ +Copyright © 2017 IBM Corp. with Reserved Font Name "Plex" + +This Font Software is licensed under the SIL Open Font License, Version 1.1. + +This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/styles/font/IBMPlexMono-Medium.woff2 b/src/styles/font/IBMPlexMono-Medium.woff2 new file mode 100644 index 000000000..dd2439862 Binary files /dev/null and b/src/styles/font/IBMPlexMono-Medium.woff2 differ diff --git a/src/styles/font/IBMPlexMono-Regular.woff2 b/src/styles/font/IBMPlexMono-Regular.woff2 new file mode 100644 index 000000000..8bdb4c4b3 Binary files /dev/null and b/src/styles/font/IBMPlexMono-Regular.woff2 differ diff --git a/src/styles/font/IBMPlexMono-SemiBold.woff2 b/src/styles/font/IBMPlexMono-SemiBold.woff2 new file mode 100644 index 000000000..a0acc15bd Binary files /dev/null and b/src/styles/font/IBMPlexMono-SemiBold.woff2 differ diff --git a/src/styles/plex-mono.css b/src/styles/plex-mono.css new file mode 100644 index 000000000..a8fb5aa1b --- /dev/null +++ b/src/styles/plex-mono.css @@ -0,0 +1,56 @@ +@font-face { + font-family: "IBM Plex Mono"; + font-weight: 400; + font-style: normal; + src: + local("IBM Plex Mono"), + url("./font/IBMPlexMono-Regular.woff2") format("woff2"); + font-display: swap; +} + +@font-face { + font-family: "IBM Plex Mono"; + font-weight: 500; + font-style: normal; + src: + local("IBM Plex Mono Medium"), + url("./font/IBMPlexMono-Medium.woff2") format("woff2"); + font-display: swap; +} + +@font-face { + font-family: "IBM Plex Mono"; + font-weight: 600; + font-style: normal; + src: + local("IBM Plex Mono SemiBold"), + url("./font/IBMPlexMono-SemiBold.woff2") format("woff2"); + font-display: swap; +} + +@font-face { + font-family: "IBM Plex Mono"; + font-weight: 700; + font-style: normal; + src: + local("IBM Plex Mono Bold"), + url("./font/IBMPlexMono-Bold.woff2") format("woff2"); + font-display: swap; +} + +/* font-size-adjust keeps mono from reading larger than Manrope at the same size */ +.font-plex, +.font-plex body { + font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace; + font-size-adjust: 0.5; + letter-spacing: -0.01em; +} + +.font-plex-data .service-block-value, +.font-plex-data .service-tags, +.font-plex-data .tabular-nums, +.font-plex-data .information-widget-resource .resource-value, +.font-plex-data .service-container { + font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace; + font-size-adjust: 0.5; +}