# MezBOX design system > Design system for the self-hosted apps on mezbox.de (Stuttgart): paper #F6F6F3 and graphite #14171C > surfaces, ink #141414, one signal yellow #FFD900 (the lid of the logo), Red Hat Display / Text / Mono. > Logo: the "Lifted Lid" box mark; wordmark always "MezBOX" (Mez 800 + BOX 500 muted), app lockups > "MezBOX / App". Light and dark follow the OS. Start with DESIGN.md (rules + tokens), then USAGE.md (how to apply it). Everything at once: https://design.mezbox.de/llms-full.txt. Machine-readable file list: https://design.mezbox.de/assets.json. ## Docs - [DESIGN.md](https://design.mezbox.de/DESIGN.md): the design system, tokens in YAML front matter + rules (Stitch format) - [USAGE.md](https://design.mezbox.de/USAGE.md): applying it to an app: snippets, component classes, checklist - [Specimen](https://design.mezbox.de/): live page built from the real CSS, logos and icons - [llms-full.txt](https://design.mezbox.de/llms-full.txt): DESIGN.md + USAGE.md + tokens.css + mezbox.css + asset list in one file ## Code - [tokens/tokens.css](https://design.mezbox.de/tokens/tokens.css): Design tokens as CSS custom properties (light + dark) - [tokens/tokens.json](https://design.mezbox.de/tokens/tokens.json): Design tokens (W3C Design Tokens format) - [css/mezbox.css](https://design.mezbox.de/css/mezbox.css): Component CSS (mz-* classes); imports tokens.css ## Logo - [logo/mezbox-logo-adaptive.svg](https://design.mezbox.de/logo/mezbox-logo-adaptive.svg): Horizontal lockup that switches light/dark by itself - [logo/mezbox-logo-mono-black.svg](https://design.mezbox.de/logo/mezbox-logo-mono-black.svg): Lockup, one colour black - [logo/mezbox-logo-mono-white.svg](https://design.mezbox.de/logo/mezbox-logo-mono-white.svg): Lockup, one colour white - [logo/mezbox-logo-on-dark.svg](https://design.mezbox.de/logo/mezbox-logo-on-dark.svg): Horizontal lockup for dark backgrounds - [logo/mezbox-logo.svg](https://design.mezbox.de/logo/mezbox-logo.svg): Horizontal lockup MezBOX, for light backgrounds (outlined text) - [logo/mezbox-mark-mono-black.svg](https://design.mezbox.de/logo/mezbox-mark-mono-black.svg): Mark, one colour black - [logo/mezbox-mark-mono-white.svg](https://design.mezbox.de/logo/mezbox-mark-mono-white.svg): Mark, one colour white - [logo/mezbox-mark-on-dark.svg](https://design.mezbox.de/logo/mezbox-mark-on-dark.svg): Mark for dark backgrounds - [logo/mezbox-mark.svg](https://design.mezbox.de/logo/mezbox-mark.svg): Mark (Lifted Lid), for light backgrounds ## App lockups - [logo/apps/mezbox-auth-on-dark.svg](https://design.mezbox.de/logo/apps/mezbox-auth-on-dark.svg): App lockup 'MezBOX / Auth' for dark backgrounds - [logo/apps/mezbox-auth.svg](https://design.mezbox.de/logo/apps/mezbox-auth.svg): App lockup 'MezBOX / Auth' for light backgrounds - [logo/apps/mezbox-cloud-on-dark.svg](https://design.mezbox.de/logo/apps/mezbox-cloud-on-dark.svg): App lockup 'MezBOX / Cloud' for dark backgrounds - [logo/apps/mezbox-cloud.svg](https://design.mezbox.de/logo/apps/mezbox-cloud.svg): App lockup 'MezBOX / Cloud' for light backgrounds - [logo/apps/mezbox-design-on-dark.svg](https://design.mezbox.de/logo/apps/mezbox-design-on-dark.svg): App lockup 'MezBOX / Design' for dark backgrounds - [logo/apps/mezbox-design.svg](https://design.mezbox.de/logo/apps/mezbox-design.svg): App lockup 'MezBOX / Design' for light backgrounds - [logo/apps/mezbox-gemeinderat-on-dark.svg](https://design.mezbox.de/logo/apps/mezbox-gemeinderat-on-dark.svg): App lockup 'MezBOX / Gemeinderat' for dark backgrounds - [logo/apps/mezbox-gemeinderat.svg](https://design.mezbox.de/logo/apps/mezbox-gemeinderat.svg): App lockup 'MezBOX / Gemeinderat' for light backgrounds - [logo/apps/mezbox-intranet-adaptive.svg](https://design.mezbox.de/logo/apps/mezbox-intranet-adaptive.svg): App lockup 'MezBOX / Intranet' that adapts to light and dark - [logo/apps/mezbox-sensor-on-dark.svg](https://design.mezbox.de/logo/apps/mezbox-sensor-on-dark.svg): App lockup 'MezBOX / Sensor' for dark backgrounds - [logo/apps/mezbox-sensor.svg](https://design.mezbox.de/logo/apps/mezbox-sensor.svg): App lockup 'MezBOX / Sensor' for light backgrounds - [logo/apps/mezbox-stundenplan-on-dark.svg](https://design.mezbox.de/logo/apps/mezbox-stundenplan-on-dark.svg): App lockup 'MezBOX / Stundenplan' for dark backgrounds - [logo/apps/mezbox-stundenplan.svg](https://design.mezbox.de/logo/apps/mezbox-stundenplan.svg): App lockup 'MezBOX / Stundenplan' for light backgrounds - [logo/apps/mezbox-terrace-on-dark.svg](https://design.mezbox.de/logo/apps/mezbox-terrace-on-dark.svg): App lockup 'MezBOX / Terrace' for dark backgrounds - [logo/apps/mezbox-terrace.svg](https://design.mezbox.de/logo/apps/mezbox-terrace.svg): App lockup 'MezBOX / Terrace' for light backgrounds - [logo/apps/mezbox-video-on-dark.svg](https://design.mezbox.de/logo/apps/mezbox-video-on-dark.svg): App lockup 'MezBOX / Video' for dark backgrounds - [logo/apps/mezbox-video.svg](https://design.mezbox.de/logo/apps/mezbox-video.svg): App lockup 'MezBOX / Video' for light backgrounds ## Icons - [icons/app-icon-maskable.svg](https://design.mezbox.de/icons/app-icon-maskable.svg): Maskable app icon (full bleed) - [icons/app-icon.svg](https://design.mezbox.de/icons/app-icon.svg): App icon: ink tile with paper body and yellow lid - [icons/apple-touch-icon.png](https://design.mezbox.de/icons/apple-touch-icon.png): Apple touch icon 180 - [icons/apps/authelia.png](https://design.mezbox.de/icons/apps/authelia.png): App tile 'authelia' (256 px PNG) - [icons/apps/authelia.svg](https://design.mezbox.de/icons/apps/authelia.svg): App tile 'authelia' (SVG) - [icons/apps/crowdsec.png](https://design.mezbox.de/icons/apps/crowdsec.png): App tile 'crowdsec' (256 px PNG) - [icons/apps/crowdsec.svg](https://design.mezbox.de/icons/apps/crowdsec.svg): App tile 'crowdsec' (SVG) - [icons/apps/design.png](https://design.mezbox.de/icons/apps/design.png): App tile 'design' (256 px PNG) - [icons/apps/design.svg](https://design.mezbox.de/icons/apps/design.svg): App tile 'design' (SVG) - [icons/apps/gemeinderat.png](https://design.mezbox.de/icons/apps/gemeinderat.png): App tile 'gemeinderat' (256 px PNG) - [icons/apps/gemeinderat.svg](https://design.mezbox.de/icons/apps/gemeinderat.svg): App tile 'gemeinderat' (SVG) - [icons/apps/home-assistant.png](https://design.mezbox.de/icons/apps/home-assistant.png): App tile 'home-assistant' (256 px PNG) - [icons/apps/home-assistant.svg](https://design.mezbox.de/icons/apps/home-assistant.svg): App tile 'home-assistant' (SVG) - [icons/apps/immich.png](https://design.mezbox.de/icons/apps/immich.png): App tile 'immich' (256 px PNG) - [icons/apps/immich.svg](https://design.mezbox.de/icons/apps/immich.svg): App tile 'immich' (SVG) - [icons/apps/l39-world.png](https://design.mezbox.de/icons/apps/l39-world.png): App tile 'l39-world' (256 px PNG) - [icons/apps/l39-world.svg](https://design.mezbox.de/icons/apps/l39-world.svg): App tile 'l39-world' (SVG) - [icons/apps/lldap.png](https://design.mezbox.de/icons/apps/lldap.png): App tile 'lldap' (256 px PNG) - [icons/apps/lldap.svg](https://design.mezbox.de/icons/apps/lldap.svg): App tile 'lldap' (SVG) - [icons/apps/nextcloud.png](https://design.mezbox.de/icons/apps/nextcloud.png): App tile 'nextcloud' (256 px PNG) - [icons/apps/nextcloud.svg](https://design.mezbox.de/icons/apps/nextcloud.svg): App tile 'nextcloud' (SVG) - [icons/apps/ntfy.png](https://design.mezbox.de/icons/apps/ntfy.png): App tile 'ntfy' (256 px PNG) - [icons/apps/ntfy.svg](https://design.mezbox.de/icons/apps/ntfy.svg): App tile 'ntfy' (SVG) - [icons/apps/portainer.png](https://design.mezbox.de/icons/apps/portainer.png): App tile 'portainer' (256 px PNG) - [icons/apps/portainer.svg](https://design.mezbox.de/icons/apps/portainer.svg): App tile 'portainer' (SVG) - [icons/apps/sensor.png](https://design.mezbox.de/icons/apps/sensor.png): App tile 'sensor' (256 px PNG) - [icons/apps/sensor.svg](https://design.mezbox.de/icons/apps/sensor.svg): App tile 'sensor' (SVG) - [icons/apps/skoda.png](https://design.mezbox.de/icons/apps/skoda.png): App tile 'skoda' (256 px PNG) - [icons/apps/skoda.svg](https://design.mezbox.de/icons/apps/skoda.svg): App tile 'skoda' (SVG) - [icons/apps/stirling-pdf.png](https://design.mezbox.de/icons/apps/stirling-pdf.png): App tile 'stirling-pdf' (256 px PNG) - [icons/apps/stirling-pdf.svg](https://design.mezbox.de/icons/apps/stirling-pdf.svg): App tile 'stirling-pdf' (SVG) - [icons/apps/stundenplan.png](https://design.mezbox.de/icons/apps/stundenplan.png): App tile 'stundenplan' (256 px PNG) - [icons/apps/stundenplan.svg](https://design.mezbox.de/icons/apps/stundenplan.svg): App tile 'stundenplan' (SVG) - [icons/apps/sunalyzer.png](https://design.mezbox.de/icons/apps/sunalyzer.png): App tile 'sunalyzer' (256 px PNG) - [icons/apps/sunalyzer.svg](https://design.mezbox.de/icons/apps/sunalyzer.svg): App tile 'sunalyzer' (SVG) - [icons/apps/tandoor.png](https://design.mezbox.de/icons/apps/tandoor.png): App tile 'tandoor' (256 px PNG) - [icons/apps/tandoor.svg](https://design.mezbox.de/icons/apps/tandoor.svg): App tile 'tandoor' (SVG) - [icons/apps/terrace.png](https://design.mezbox.de/icons/apps/terrace.png): App tile 'terrace' (256 px PNG) - [icons/apps/terrace.svg](https://design.mezbox.de/icons/apps/terrace.svg): App tile 'terrace' (SVG) - [icons/apps/teslamate.png](https://design.mezbox.de/icons/apps/teslamate.png): App tile 'teslamate' (256 px PNG) - [icons/apps/teslamate.svg](https://design.mezbox.de/icons/apps/teslamate.svg): App tile 'teslamate' (SVG) - [icons/apps/traefik.png](https://design.mezbox.de/icons/apps/traefik.png): App tile 'traefik' (256 px PNG) - [icons/apps/traefik.svg](https://design.mezbox.de/icons/apps/traefik.svg): App tile 'traefik' (SVG) - [icons/apps/uptime-kuma.png](https://design.mezbox.de/icons/apps/uptime-kuma.png): App tile 'uptime-kuma' (256 px PNG) - [icons/apps/uptime-kuma.svg](https://design.mezbox.de/icons/apps/uptime-kuma.svg): App tile 'uptime-kuma' (SVG) - [icons/apps/video.png](https://design.mezbox.de/icons/apps/video.png): App tile 'video' (256 px PNG) - [icons/apps/video.svg](https://design.mezbox.de/icons/apps/video.svg): App tile 'video' (SVG) - [icons/favicon-256.png](https://design.mezbox.de/icons/favicon-256.png): Mark as 256 px PNG with transparency (source of favicon.ico) - [icons/favicon.ico](https://design.mezbox.de/icons/favicon.ico): Favicon 16/32/48 - [icons/favicon.svg](https://design.mezbox.de/icons/favicon.svg): Favicon (adapts to dark browser UI) - [icons/icon-192.png](https://design.mezbox.de/icons/icon-192.png): PWA icon 192 - [icons/icon-512.png](https://design.mezbox.de/icons/icon-512.png): PWA icon 512 - [icons/icon-maskable-512.png](https://design.mezbox.de/icons/icon-maskable-512.png): PWA maskable icon 512 - [icons/site.webmanifest](https://design.mezbox.de/icons/site.webmanifest): Example web manifest ## Fonts - [fonts/](https://design.mezbox.de/fonts/): Red Hat Display, Text, Mono (variable, woff2 + ttf, SIL OFL 1.1) ## Optional - [assets.json](https://design.mezbox.de/assets.json): every published file with URL and description --- # DESIGN.md --- version: alpha name: MezBOX description: >- Design system for the self-hosted apps on mezbox.de. Paper-and-graphite neutrals, a single signal yellow taken from the lid of the logo, and the Red Hat type family. Built for small, dense tools that a family and one admin use every day. colors: primary: "#141414" on-primary: "#F6F6F3" accent: "#FFD900" accent-hover: "#F2CE00" on-accent: "#141414" background: "#F6F6F3" surface: "#FFFFFF" surface-sunken: "#EFEFEA" surface-hover: "#F1F1EC" border: "#E2E2DC" text: "#141414" text-muted: "#6B6F78" ink-2: "#4A505B" success: "#1F9D55" success-text: "#177A42" warning: "#E5701A" warning-text: "#B2510C" danger: "#D93636" danger-text: "#B42323" info: "#3A6FD8" info-text: "#2B57B0" dark-background: "#14171C" dark-surface: "#1C2027" dark-surface-sunken: "#101318" dark-surface-hover: "#232831" dark-border: "#2C323C" dark-text: "#E8EBF0" dark-text-muted: "#8A93A3" dark-ink-2: "#9AA3B2" dark-accent-hover: "#FFE14D" dark-success-text: "#4CC77F" dark-warning-text: "#F39A52" dark-danger-text: "#F07070" dark-info-text: "#7FA3F0" typography: display: fontFamily: Red Hat Display fontSize: 40px fontWeight: 800 lineHeight: 1.1 letterSpacing: -0.03em h1: fontFamily: Red Hat Display fontSize: 30px fontWeight: 800 lineHeight: 1.15 letterSpacing: -0.03em h2: fontFamily: Red Hat Display fontSize: 24px fontWeight: 800 lineHeight: 1.2 letterSpacing: -0.025em h3: fontFamily: Red Hat Display fontSize: 18px fontWeight: 700 lineHeight: 1.3 letterSpacing: -0.01em title: fontFamily: Red Hat Display fontSize: 15px fontWeight: 700 lineHeight: 1.3 letterSpacing: -0.01em body-lg: fontFamily: Red Hat Text fontSize: 16px fontWeight: 400 lineHeight: 1.55 body: fontFamily: Red Hat Text fontSize: 14px fontWeight: 400 lineHeight: 1.5 small: fontFamily: Red Hat Text fontSize: 12.5px fontWeight: 400 lineHeight: 1.45 button: fontFamily: Red Hat Text fontSize: 13.5px fontWeight: 600 lineHeight: 1 label: fontFamily: Red Hat Mono fontSize: 11.5px fontWeight: 500 lineHeight: 1.3 letterSpacing: 0.06em mono: fontFamily: Red Hat Mono fontSize: 12px fontWeight: 400 lineHeight: 1.4 rounded: xs: 4px sm: 6px md: 10px lg: 14px xl: 18px full: 9999px spacing: "1": 4px "2": 8px "3": 12px "4": 16px "5": 20px "6": 24px "8": 32px "12": 48px components: button-primary: backgroundColor: "{colors.accent}" textColor: "{colors.on-accent}" typography: "{typography.button}" rounded: "{rounded.md}" padding: 9px 14px button-primary-hover: backgroundColor: "{colors.accent-hover}" button-ink: backgroundColor: "{colors.primary}" textColor: "{colors.on-primary}" typography: "{typography.button}" rounded: "{rounded.md}" padding: 9px 14px button-secondary: backgroundColor: "{colors.surface}" textColor: "{colors.text}" typography: "{typography.button}" rounded: "{rounded.md}" padding: 9px 14px button-ghost: backgroundColor: transparent textColor: "{colors.text-muted}" typography: "{typography.button}" rounded: "{rounded.md}" padding: 9px 14px input: backgroundColor: "{colors.surface}" textColor: "{colors.text}" typography: "{typography.body}" rounded: "{rounded.md}" padding: 10px 12px card: backgroundColor: "{colors.surface}" rounded: "{rounded.lg}" padding: 16px badge-new: backgroundColor: "{colors.accent}" textColor: "{colors.on-accent}" rounded: "{rounded.sm}" padding: 0 7px badge-soft: backgroundColor: "{colors.surface-sunken}" textColor: "{colors.text-muted}" rounded: "{rounded.sm}" padding: 0 7px app-bar: backgroundColor: "{colors.surface}" textColor: "{colors.text}" padding: 12px 20px app-tile-own: backgroundColor: "{colors.primary}" textColor: "{colors.on-primary}" rounded: 23.4% size: 40px app-tile-third-party: backgroundColor: "{colors.surface-sunken}" textColor: "{colors.text}" rounded: 23.4% size: 40px tab-active: textColor: "{colors.text}" dialog: backgroundColor: "{colors.surface}" rounded: "{rounded.xl}" padding: 28px --- # MezBOX ## Overview MezBOX is the home server behind mezbox.de. It runs a mix of family apps (Nextcloud, Immich, Tandoor), smart-home and energy tools (Home Assistant, Sunalyzer, TeslaMate) and a handful of apps built in-house (Gemeinderat Scanner, MezBOX Video, Stundenplan, MezBOX Sensor). This system gives them one face. The character is **a well-labelled box in a tidy workshop**: calm paper and graphite surfaces, crisp type, and one bright yellow strip of tape that marks what matters. It's a private tool, not a marketing site. Screens are dense, scannable and honest about state. Personality comes from the mark, the yellow and the heavy Red Hat Display headings. Everything else stays quiet. The brand descends from the original 2022 mezbox.de page (white Clarendon on black, yellow #FFE400). It keeps the black-and-yellow contrast and the "BOX", and swaps the slab serif for a modern grotesque. ### Logo - **Mark: the Lifted Lid.** A box whose top edge dips into an "M", with the lid lifted and tilted 7° above it. The body is ink (`#141414`, or `#E8EBF0` on dark). The lid is always yellow `#FFD900`, except in one-color versions. - **Wordmark:** `MezBOX`, always one word with no space. "Mez" is Red Hat Display 800 and "BOX" is Red Hat Display 500 in `text-muted`. Never write "Mezbox", "MEZBOX" or "Mez BOX". - **App lockup:** `MezBOX / Video`. The app name is 500 weight in `text-muted`, and the slash is in `border` color. - **Clear space:** at least the lid's height (≈ 0.2 × mark size) on all sides. The smallest mark size is 16 px, and the smallest lockup is 18 px font size. - Files are in `logo/` (mark, lockup, on-dark and mono versions) and `logo/apps/` (app lockups). Icons are in `icons/`. ## Colors The palette is **Paper & Graphite with one yellow**. Neutrals carry a slight cool-green bias (paper) or blue bias (graphite) so they never look like default greys. - **Ink `#141414` / Paper `#F6F6F3`:** the base pair. Ink is text, the mark body, the ink button and own-app tiles. Paper is the page. - **Yellow `#FFD900` (accent):** the lid. It's reserved for exactly these jobs: the primary action, the focus ring, "new/unread" markers, the active tab underline, link underlines and detection boxes in Video. Text on yellow is always ink, in both themes. Never put yellow text on paper (contrast 1.3:1). - **Surfaces:** `surface` (#FFF) for cards and bars, `surface-sunken` (#EFEFEA) for wells, search and third-party tiles, `surface-hover` for row and card hover. - **Status:** success green, warning **orange** (deliberately not yellow, so a warning never looks like brand), danger red and info blue. Each has a `-text` variant tuned for body-size text on surfaces. Use the base value for dots, borders and tinted badge backgrounds. - **Dark theme: graphite, not black.** `#14171C` background and `#1C2027` surfaces. Yellow stays the same, and status text uses the `-text-dark` values from `tokens/tokens.json`. Apps follow the OS setting by default. MezBOX Video defaults to dark. `data-theme="light|dark"` on `` overrides. All text pairs meet WCAG AA: ink on paper is 17:1, muted on paper 4.65:1, ink on yellow 13.3:1, dark text on graphite 15:1 and dark muted on graphite 5.8:1. Status `-text` colors are 5.1:1 or better on their surface. ## Typography One family, three cuts, all OFL-licensed and self-hosted from `fonts/`: - **Red Hat Display** for headings, the wordmark, card titles and big numbers. Weights 800 and 700, tracked tight (−0.025 to −0.03em). - **Red Hat Text** for everything people read: body, buttons, form fields. Weights 400, 500 and 600. - **Red Hat Mono** for things machines produce: hostnames, timestamps, IDs, file sizes, counts in tables, and the small uppercase section labels (`label`, +0.06em). The scale is display 40, h1 30, h2 24, h3 18, title 15, body 14 (16 for reading-heavy pages) and small 12.5. Headings use `text-wrap: balance`. Any column of digits gets `font-variant-numeric: tabular-nums`. German UIs stay in German. Write short, direct labels ("Original öffnen", "Als gelesen markieren"). ## Layout - Use a **4 px spacing grid** (`spacing.1` to `spacing.12`). Use gaps on flex/grid rather than margins. - **App bar** on every own app: lockup on the left (`MezBOX / AppName`), then actions and the avatar on the right. The markup is in USAGE.md section 1 (https://design.mezbox.de/USAGE.md). It's 52–56 px high, with the surface color and a bottom border. - **Content width:** dashboards are fluid up to 1280 px. Reading views keep text to about 75 characters. - **Side nav** (for lists like Gemeinderat search terms) is 240 px on the surface, and stacks above the content under 860 px. - **Card grids:** `repeat(auto-fill, minmax(230px, 1fr))` with 10–12 px gaps. - Phone layouts are first-class (Stundenplan, ntfy pushes). They use a 16 px gutter and a bottom tab bar whose active tab is marked with a yellow bar. ## Elevation & Depth The system is mostly **flat with 1 px borders**. Depth is shown by surface steps (sunken → bg → surface), not shadows. Only things that float above the page (menus, dialogs, toasts, the login box on a photo background) get `--mz-shadow-overlay`. Hover states change the border and background, not the elevation. ## Shapes - Radii are `md` 10 px for buttons and inputs, `lg` 14 px for cards, `xl` 18 px for dialogs and login boxes, and `sm` 6 px for badges. Pills and avatars are fully round. - **App tiles** are 23.4% radius squares (15 on a 64 grid): - **Own apps wear the lid:** an ink tile with a tilted yellow bar at the top and white initials (Red Hat Display 800). - **Third-party apps** get a neutral sunken tile with ink initials. They never get the lid. - Tiles live in `icons/apps/` (SVG + 256 px PNG). Add new ones in `scripts/build_assets.py`. - The lid's 7° tilt is the only rotated element in the system. Don't tilt anything else. ## Components - **Buttons:** at most **one primary (yellow) per view**. Use `ink` for strong secondary actions such as Update and Add, `secondary` (outlined) for everything else and `ghost` in app bars. Use `danger` only for destructive confirmation. - **Inputs:** surface fill, 1 px border, 10 px radius. On focus the border turns ink and gets a 3 px yellow ring at 70% opacity. The same ring is used for all keyboard focus. - **Links:** text color with a 2 px yellow underline. The underline turns ink on hover. - **Badges:** `new` is yellow with ink text (for unread counts and "Neu"). `soft` is sunken with muted text (for tags and search terms). Status badges are tinted with `-text` colors. - **Status dots:** 8 px. Green means up, orange slow or degraded, red down and muted idle. - **Unread card:** a 4 px yellow left edge until it's marked as read. - **Tabs:** muted labels with a 2 px yellow underline on the active tab. **Segmented controls** and **filter pills** show the active item as an ink fill. - **Detection boxes (Video):** 2 px yellow outline with a yellow label tab showing the class and confidence (`person 0.91`). - **Push notifications (ntfy):** topic `mezbox-`, with the app tile PNG as the icon. Reference implementation: `css/mezbox.css` (prefix `mz-`), loading `tokens/tokens.css`. ## Do's and Don'ts - **Do** use yellow as a signal. If more than two or three yellow things are on screen, one of them shouldn't be yellow. - **Do** keep "MezBOX" as one word with the weight change at "BOX". - **Do** put hostnames, times and IDs in Red Hat Mono. - **Do** design dark mode on purpose: graphite surfaces, same yellow. - **Don't** put white or yellow text on yellow. Text on the accent is always ink. - **Don't** use yellow for warnings. Warnings are orange. - **Don't** give third-party apps the lid tile, or redraw their logos. - **Don't** use pure black (`#000`) backgrounds, gradients or glassmorphism. - **Don't** add drop shadows to cards or tilt anything except the lid. - **Don't** set body text in Red Hat Display, or headings in Red Hat Text. --- # USAGE.md # Using the MezBOX design system How to give an app on mezbox.de the MezBOX look. The rules live in [DESIGN.md](https://design.mezbox.de/DESIGN.md); this page is the practical how-to. All files below are served from **https://design.mezbox.de** with CORS enabled, so an app can link them directly or copy them into its own `static/` folder (preferred for apps that must work offline). ## 1. Own apps (full system) ```html
MezBOXVideo SM
Neu
…
``` Component classes (all prefixed `mz-`): `mz-appbar`, `mz-lockup` (+ `mz-box`, `mz-app`), `mz-avatar`, `mz-btn` (`--primary` yellow, `--ink`, `--secondary`, `--ghost`, `--danger`, `--block`), `mz-field`, `mz-input`, `mz-help`, `mz-error`, `mz-badge` (`--new`, `--soft`, `--success`, `--warning`, `--danger`), `mz-dot` (`--warning`, `--danger`, `--idle`), `mz-pill[aria-pressed]`, `mz-card` (`--interactive`, `--unread`), `mz-overlay`, `mz-tabs` / `mz-tab[aria-selected]`, `mz-seg`, `mz-tile` (`--own`, `--third`), `mz-table`, `mz-detect[data-label]`, `mz-label`, `mz-mono`, `mz-muted`, `mz-h1..3`. If an app keeps its own CSS, map its variables onto the tokens instead (see `tokens/tokens.css`): paper `#F6F6F3` / graphite `#14171C`, ink `#141414` / `#E8EBF0`, muted `#6B6F78` / `#8A93A3`, border `#E2E2DC` / `#2C323C`, yellow `#FFD900` with ink text, radii 10 / 14 / 18 px, Red Hat Display (headings, wordmark) / Text (UI) / Mono (numbers, times, hosts, labels). Font files: `fonts/RedHatDisplay[wght].woff2` etc. URL-encode the brackets when linking (`RedHatDisplay%5Bwght%5D.woff2`) in tools that do not; browsers accept them as they are, and `css/mezbox.css` loads the fonts for you. Light and dark follow the OS (`prefers-color-scheme`); `data-theme="light|dark"` on `` overrides. ## 2. Wordmark and logo rules (short) - Always **MezBOX**, one word: "Mez" weight 800, "BOX" weight 500 in muted grey. App lockup: `MezBOX / App` (app name 500 muted, slash in border colour). - Use the SVGs from `logo/` (text is outlined, no font needed). On dark backgrounds use `*-on-dark.svg`; `mezbox-logo-adaptive.svg` switches by itself. - Own apps use an **own-app tile** (ink square, tilted yellow lid bar, white initials) from `icons/apps/`. Third-party apps get a neutral tile; never give them the lid. ### Making an app tile for a new app Own-app tiles are 64x64 SVGs: an ink square with a tilted yellow lid bar and two initials. The initials are Red Hat Display 800 in paper; the published tiles use outlined paths, but live text works too: ```svg Vi ``` Third-party tiles: `` with ink initials, and no lid. Use 3 letters for very short names ("PDF") at a smaller size. The mark itself on a 64 grid (body ink or `#E8EBF0` on dark, lid always `#FFD900`): ```svg ``` ## 3. Yellow budget Yellow `#FFD900` is a signal, not a fill colour: primary button (one per view), focus ring, link underline, "new/unread" markers, active tab underline, activity ticks / detection boxes. Text on yellow is always ink. Warnings are orange `#E5701A`, never yellow. ## 4. Third-party apps Theme only what the app allows (logo, favicon, primary colour, background). If an app darkens yellow for contrast (Nextcloud does), use ink `#141414` as its primary colour instead. Ready-made kits for Authelia, Nextcloud, Home Assistant, Homer and ntfy live in the private repo `smezger/mezbox-design` under `themes/`. ## 5. Checklist for a new app 1. Favicon + touch icon + `MezBOX / App` lockup in the app bar. 2. Fonts: Red Hat Display / Text / Mono (from `fonts/` or via `css/mezbox.css`). 3. Paper/graphite surfaces, 1 px borders, no card shadows; shadow only for overlays. 4. One yellow primary action per view; everything else ink / secondary / ghost. 5. Times, sizes, hostnames, IDs in Red Hat Mono with tabular numbers. 6. Check light and dark, and phone width. --- # tokens/tokens.css ```css /* MezBOX design tokens - generated from DESIGN.md. Light is the default; dark follows the OS, and data-theme="light|dark" on overrides both. */ @font-face { font-family: "Red Hat Display"; src: url("../fonts/RedHatDisplay[wght].woff2") format("woff2"); font-weight: 300 900; font-display: swap; } @font-face { font-family: "Red Hat Text"; src: url("../fonts/RedHatText[wght].woff2") format("woff2"); font-weight: 300 700; font-display: swap; } @font-face { font-family: "Red Hat Mono"; src: url("../fonts/RedHatMono[wght].woff2") format("woff2"); font-weight: 300 700; font-display: swap; } :root { /* color - surfaces */ --mz-bg: #F6F6F3; --mz-surface: #FFFFFF; --mz-sunken: #EFEFEA; --mz-hover: #F1F1EC; --mz-border: #E2E2DC; /* color - content */ --mz-text: #141414; --mz-text-muted: #6B6F78; --mz-ink-2: #4A505B; /* color - brand */ --mz-accent: #FFD900; --mz-accent-hover: #F2CE00; --mz-on-accent: #141414; --mz-focus: rgb(255 217 0 / 0.7); /* color - status (fill = dots, borders, badge tint; text = readable on surface) */ --mz-success: #1F9D55; --mz-success-text: #177A42; --mz-warning: #E5701A; --mz-warning-text: #B2510C; --mz-danger: #D93636; --mz-danger-text: #B42323; --mz-info: #3A6FD8; --mz-info-text: #2B57B0; /* type */ --mz-font-display: "Red Hat Display", system-ui, -apple-system, "Segoe UI", sans-serif; --mz-font-text: "Red Hat Text", system-ui, -apple-system, "Segoe UI", sans-serif; --mz-font-mono: "Red Hat Mono", ui-monospace, "Cascadia Mono", Consolas, monospace; --mz-fs-display: 2.5rem; /* 40 */ --mz-fs-h1: 1.875rem; /* 30 */ --mz-fs-h2: 1.5rem; /* 24 */ --mz-fs-h3: 1.125rem; /* 18 */ --mz-fs-title: 0.9375rem; /* 15 */ --mz-fs-body-lg: 1rem; /* 16 */ --mz-fs-body: 0.875rem; /* 14 */ --mz-fs-small: 0.78125rem;/* 12.5 */ --mz-fs-label: 0.71875rem;/* 11.5 */ --mz-fs-mono: 0.75rem; /* 12 */ --mz-tracking-display: -0.03em; --mz-tracking-heading: -0.025em; --mz-tracking-label: 0.06em; /* space (4 px base) */ --mz-space-1: 4px; --mz-space-2: 8px; --mz-space-3: 12px; --mz-space-4: 16px; --mz-space-5: 20px; --mz-space-6: 24px; --mz-space-8: 32px; --mz-space-12: 48px; /* radius */ --mz-radius-xs: 4px; /* kbd, tiny tags */ --mz-radius-sm: 6px; /* badges */ --mz-radius-md: 10px; /* buttons, inputs */ --mz-radius-lg: 14px; /* cards */ --mz-radius-xl: 18px; /* dialogs, login box */ --mz-radius-full: 999px; --mz-radius-tile: 23.4%; /* app tiles: 15 on a 64 grid */ /* elevation - flat by default; shadow only for things that float */ --mz-shadow-overlay: 0 18px 40px -24px rgb(0 0 0 / 0.35), 0 2px 6px rgb(0 0 0 / 0.06); --mz-ring: 0 0 0 3px var(--mz-focus); /* motion */ --mz-ease: cubic-bezier(.2, .7, .2, 1); --mz-dur-fast: 120ms; --mz-dur: 200ms; color-scheme: light; } @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { --mz-bg: #14171C; --mz-surface: #1C2027; --mz-sunken: #101318; --mz-hover: #232831; --mz-border: #2C323C; --mz-text: #E8EBF0; --mz-text-muted: #8A93A3; --mz-ink-2: #9AA3B2; --mz-accent-hover: #FFE14D; --mz-success-text: #4CC77F; --mz-warning-text: #F39A52; --mz-danger-text: #F07070; --mz-info-text: #7FA3F0; --mz-shadow-overlay: 0 18px 40px -20px rgb(0 0 0 / 0.7), 0 2px 6px rgb(0 0 0 / 0.3); color-scheme: dark; } } :root[data-theme="dark"] { --mz-bg: #14171C; --mz-surface: #1C2027; --mz-sunken: #101318; --mz-hover: #232831; --mz-border: #2C323C; --mz-text: #E8EBF0; --mz-text-muted: #8A93A3; --mz-ink-2: #9AA3B2; --mz-accent-hover: #FFE14D; --mz-success-text: #4CC77F; --mz-warning-text: #F39A52; --mz-danger-text: #F07070; --mz-info-text: #7FA3F0; --mz-shadow-overlay: 0 18px 40px -20px rgb(0 0 0 / 0.7), 0 2px 6px rgb(0 0 0 / 0.3); color-scheme: dark; } ``` --- # css/mezbox.css ```css /* MezBOX components for own apps. Load after tokens.css. Classes are prefixed mz- so they can live next to an app's own CSS. */ @import url("../tokens/tokens.css"); /* ---- base ---- */ .mz, body.mz { background: var(--mz-bg); color: var(--mz-text); font: 400 var(--mz-fs-body)/1.5 var(--mz-font-text); -webkit-font-smoothing: antialiased; } .mz :focus-visible { outline: none; box-shadow: var(--mz-ring); border-radius: var(--mz-radius-sm); } .mz h1, .mz .mz-h1 { font: 800 var(--mz-fs-h1)/1.15 var(--mz-font-display); letter-spacing: var(--mz-tracking-display); margin: 0; text-wrap: balance; } .mz h2, .mz .mz-h2 { font: 800 var(--mz-fs-h2)/1.2 var(--mz-font-display); letter-spacing: var(--mz-tracking-heading); margin: 0; text-wrap: balance; } .mz h3, .mz .mz-h3 { font: 700 var(--mz-fs-h3)/1.3 var(--mz-font-display); letter-spacing: -0.01em; margin: 0; } .mz-muted { color: var(--mz-text-muted); } .mz-mono { font: 400 var(--mz-fs-mono)/1.4 var(--mz-font-mono); color: var(--mz-text-muted); font-variant-numeric: tabular-nums; } .mz-label { font: 500 var(--mz-fs-label)/1.3 var(--mz-font-mono); letter-spacing: var(--mz-tracking-label); text-transform: uppercase; color: var(--mz-text-muted); } .mz a, .mz-link { color: var(--mz-text); text-decoration: underline; text-decoration-color: var(--mz-accent); text-decoration-thickness: 2px; text-underline-offset: 3px; } .mz a:hover, .mz-link:hover { text-decoration-color: var(--mz-text); } /* ---- wordmark (live text; use logo/*.svg where the font may be missing) ---- */ .mz .mz-lockup { text-decoration: none; } .mz-lockup { display: inline-flex; align-items: center; gap: .32em; font: 800 1.125rem/1 var(--mz-font-display); letter-spacing: var(--mz-tracking-display); color: var(--mz-text); white-space: nowrap; text-decoration: none; } .mz-lockup img, .mz-lockup svg { width: 1.2em; height: 1.2em; flex: none; } .mz-lockup .mz-box { font-weight: 500; color: var(--mz-text-muted); } .mz-lockup .mz-app { font-weight: 500; color: var(--mz-text-muted); letter-spacing: -0.01em; margin-left: .15em; } .mz-lockup .mz-app::before { content: "/"; margin-right: .35em; color: var(--mz-border); } /* ---- app bar ---- */ .mz-appbar { display: flex; align-items: center; gap: var(--mz-space-3); padding: var(--mz-space-3) var(--mz-space-5); background: var(--mz-surface); border-bottom: 1px solid var(--mz-border); } .mz-appbar .mz-spacer { flex: 1; } .mz-avatar { width: 30px; height: 30px; border-radius: 50%; background: var(--mz-text); color: var(--mz-bg); display: grid; place-items: center; font: 700 12px var(--mz-font-display); flex: none; } /* ---- buttons ---- */ .mz-btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; font: 600 .84375rem/1 var(--mz-font-text); padding: 9px 14px; border-radius: var(--mz-radius-md); border: 1px solid transparent; cursor: pointer; white-space: nowrap; text-decoration: none; transition: background var(--mz-dur-fast) var(--mz-ease), border-color var(--mz-dur-fast) var(--mz-ease); } .mz-btn--primary { background: var(--mz-accent); color: var(--mz-on-accent); } .mz-btn--primary:hover { background: var(--mz-accent-hover); } .mz-btn--ink { background: var(--mz-text); color: var(--mz-bg); } .mz-btn--secondary { background: var(--mz-surface); color: var(--mz-text); border-color: var(--mz-border); } .mz-btn--secondary:hover { border-color: var(--mz-text-muted); } .mz-btn--ghost { background: transparent; color: var(--mz-text-muted); } .mz-btn--ghost:hover { background: var(--mz-hover); color: var(--mz-text); } .mz-btn--danger { background: var(--mz-danger); color: #fff; } .mz-btn--block { width: 100%; padding: 12px 14px; } .mz-btn:disabled { opacity: .45; cursor: not-allowed; } /* ---- form ---- */ .mz-field { display: grid; gap: 6px; font: 500 .8125rem/1.3 var(--mz-font-text); } .mz-input { font: 400 var(--mz-fs-body)/1.3 var(--mz-font-text); color: var(--mz-text); background: var(--mz-surface); border: 1px solid var(--mz-border); border-radius: var(--mz-radius-md); padding: 10px 12px; } .mz-input::placeholder { color: var(--mz-text-muted); } .mz-input:focus { outline: none; border-color: var(--mz-text); box-shadow: var(--mz-ring); } .mz-input[aria-invalid="true"] { border-color: var(--mz-danger); } .mz-help { font-size: var(--mz-fs-small); color: var(--mz-text-muted); font-weight: 400; } .mz-error { font-size: var(--mz-fs-small); color: var(--mz-danger-text); font-weight: 500; } /* ---- badges, chips, status ---- */ .mz-badge { display: inline-flex; align-items: center; gap: 5px; font: 600 .71875rem/1.5 var(--mz-font-text); padding: 0 7px; border-radius: var(--mz-radius-sm); white-space: nowrap; } .mz-badge--new { background: var(--mz-accent); color: var(--mz-on-accent); } .mz-badge--soft { background: var(--mz-sunken); color: var(--mz-text-muted); border: 1px solid var(--mz-border); } .mz-badge--danger { background: color-mix(in srgb, var(--mz-danger) 14%, var(--mz-surface)); color: var(--mz-danger-text); } .mz-badge--warning { background: color-mix(in srgb, var(--mz-warning) 16%, var(--mz-surface)); color: var(--mz-warning-text); } .mz-badge--success { background: color-mix(in srgb, var(--mz-success) 14%, var(--mz-surface)); color: var(--mz-success-text); } .mz-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; flex: none; background: var(--mz-success); } .mz-dot--warning { background: var(--mz-warning); } .mz-dot--danger { background: var(--mz-danger); } .mz-dot--idle { background: var(--mz-text-muted); } .mz-pill { display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--mz-border); border-radius: var(--mz-radius-full); padding: 4px 11px; font-size: var(--mz-fs-small); color: var(--mz-text-muted); background: transparent; cursor: pointer; } .mz-pill[aria-pressed="true"] { background: var(--mz-text); color: var(--mz-bg); border-color: var(--mz-text); } /* ---- surfaces ---- */ .mz-card { background: var(--mz-surface); border: 1px solid var(--mz-border); border-radius: var(--mz-radius-lg); padding: var(--mz-space-4); } .mz-card--interactive { transition: border-color var(--mz-dur-fast) var(--mz-ease), background var(--mz-dur-fast) var(--mz-ease); } .mz-card--interactive:hover { border-color: var(--mz-text); background: var(--mz-hover); } .mz-card--unread { border-left: 4px solid var(--mz-accent); padding-left: calc(var(--mz-space-4) - 3px); } .mz-overlay { background: var(--mz-surface); border: 1px solid var(--mz-border); border-radius: var(--mz-radius-xl); box-shadow: var(--mz-shadow-overlay); } /* ---- tabs & segmented ---- */ .mz-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--mz-border); } .mz-tab { padding: 8px 12px; color: var(--mz-text-muted); border-bottom: 2px solid transparent; margin-bottom: -1px; background: none; border-top: 0; border-left: 0; border-right: 0; font: inherit; cursor: pointer; } .mz-tab[aria-selected="true"] { color: var(--mz-text); font-weight: 600; border-bottom-color: var(--mz-accent); } .mz-seg { display: inline-flex; padding: 2px; border: 1px solid var(--mz-border); border-radius: var(--mz-radius-md); background: var(--mz-surface); } .mz-seg > * { padding: 4px 10px; border-radius: 8px; border: 0; background: none; color: var(--mz-text-muted); font: inherit; font-size: var(--mz-fs-small); cursor: pointer; } .mz-seg > [aria-pressed="true"] { background: var(--mz-text); color: var(--mz-bg); } /* ---- app tiles (launcher, notifications) ---- */ .mz-tile { width: 40px; aspect-ratio: 1; border-radius: var(--mz-radius-tile); display: grid; place-items: center; font: 800 14px/1 var(--mz-font-display); letter-spacing: -0.02em; position: relative; overflow: hidden; flex: none; } .mz-tile--third { background: var(--mz-sunken); color: var(--mz-text); border: 1px solid var(--mz-border); } .mz-tile--own { background: var(--mz-text); color: var(--mz-bg); padding-top: 6px; } .mz-tile--own::before { content: ""; position: absolute; left: 14%; right: 14%; top: 12%; height: 11%; border-radius: 2px; background: var(--mz-accent); transform: rotate(-7deg); } /* ---- table ---- */ .mz-table { width: 100%; border-collapse: collapse; font-size: var(--mz-fs-body); } .mz-table th { font: 500 var(--mz-fs-label)/1.3 var(--mz-font-mono); letter-spacing: var(--mz-tracking-label); text-transform: uppercase; color: var(--mz-text-muted); text-align: left; padding: 8px 12px; border-bottom: 1px solid var(--mz-border); } .mz-table td { padding: 10px 12px; border-bottom: 1px solid var(--mz-border); font-variant-numeric: tabular-nums; } .mz-table tr:hover td { background: var(--mz-hover); } /* ---- detection overlay (video) ---- */ .mz-detect { position: absolute; border: 2px solid var(--mz-accent); border-radius: 3px; } .mz-detect::before { content: attr(data-label); position: absolute; top: -17px; left: -2px; background: var(--mz-accent); color: var(--mz-on-accent); font: 600 9.5px/1 var(--mz-font-text); padding: 3px 4px; border-radius: 3px 3px 0 0; white-space: nowrap; } @media (prefers-reduced-motion: reduce) { .mz * { transition: none !important; animation: none !important; } } ``` --- # Mark geometry (64x64 grid) ```svg MezBOX ```