diff --git a/DESIGN.md b/DESIGN.md index a1c79fcb61..010d47e9f5 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -29,8 +29,9 @@ change. soft outer box with a small title row. - **Tactile buttons.** Buttons and navigation icon tiles have a 2px bottom edge. They lift 1px on hover and press down on click. -- **Near-black, not black.** Dark mode is a very dark gray with a faint brand - tint. Light mode is pure neutral gray. +- **Dark gray, not black.** Dark mode is a dark gray with a faint brand tint + (page canvas about sRGB 20,17,26, never near-black). Light mode is pure + neutral gray. - **One accent per theme.** Purple in light mode, yellow in dark mode (purple has too little contrast on dark surfaces). Everything else is neutral. - **Quiet text.** Sentence case. A clear text ladder: primary, dim, faint. @@ -92,9 +93,9 @@ follow the theme or the tint. } .dark { - /* Surfaces (dark: near-black with a faint purple tint, hue 300) */ - --ui-canvas: oklch(14.6% 0.018 300); - --ui-panel: oklch(19.2% 0.03 300); + /* Surfaces (dark: dark gray with a faint purple tint, hue 300) */ + --ui-canvas: oklch(18.6% 0.018 300); /* sRGB 20,17,26 */ + --ui-panel: oklch(23.2% 0.03 300); /* sRGB 31,26,41: header bar, card and table headers */ --ui-card-bg: rgb(255 255 255 / 6%); --ui-card-border: rgb(255 255 255 / 8%); --ui-well-bg: rgb(0 0 0 / 34%); @@ -102,9 +103,9 @@ follow the theme or the tint. --ui-well-shadow: 0 1px 2px rgb(0 0 0 / 25%); --ui-control-bg: rgb(255 255 255 / 3%); --ui-control-border: rgb(255 255 255 / 8%); - --ui-menu-bg: oklch(25.2% 0.018 300); - --ui-menu-border: oklch(32% 0.012 300); - --ui-hover: oklch(29.31% 0.018 300); + --ui-menu-bg: oklch(29.2% 0.018 300); + --ui-menu-border: oklch(36% 0.012 300); + --ui-hover: oklch(33.31% 0.018 300); --ui-divider: rgb(255 255 255 / 7%); --ui-chrome-border: rgb(255 255 255 / 4%); @@ -135,7 +136,13 @@ Rules for the tokens: Keep it small: chroma 0.012 for borders, 0.018 for surfaces, 0.03 for the header. On near-black, pick the hue by the rendered sRGB value: red must be a little above green, or the tint looks blue (hue 300 renders the canvas as - sRGB 11,9,17). + sRGB 20,17,26). +- **Dark lightness level.** The canvas is at 18.6% and the header panel at + 23.2%. Do not make dark mode darker than this; the page looks too heavy. To + change the level, move every dark surface and border token by the same + amount (canvas, panel, menu, hover, borders, and the Coolify surface ladder), + so the steps between layers stay the same. If you move only some of them, a + hover fill can become darker than its menu. - The primary buttons use purple in both themes. Only focus rings, active navigation, and other accent states change to yellow in dark mode. @@ -176,6 +183,9 @@ Class names below are examples. Keep the values and structure. - Three layers: the canvas (page and sidebar, `--ui-canvas`), the top header bar (`--ui-panel`, 48px high), and the cards on the canvas. +- In dark mode, the header bar, the outer box of a top-level card, and the + header row of a table use the same color (`--ui-panel`). The card wells and + table rows are the darker content layer below them. - The sidebar is the canvas color and has a 1px `--ui-chrome-border` right edge. The brand block (product name and version) at the top of the sidebar column also uses the canvas color, with a 1px bottom border. @@ -224,6 +234,7 @@ Class names below are examples. Keep the values and structure. background: var(--ui-card-bg); box-shadow: var(--ui-card-shadow); overflow: visible; /* menus and tooltips must escape */ } +.dark .card { background: var(--ui-panel); } /* top-level card: same as the header bar */ .card-header { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 16px; padding: 4px 8px 10px; @@ -313,14 +324,20 @@ Class names below are examples. Keep the values and structure. ### Tables -- Header row: 40px, 13px medium, `--ui-fg-faint`, a faint fill - (`rgb(0 0 0 / 2%)` light, `rgb(255 255 255 / 2%)` dark), and a 1px - `--ui-divider` bottom border. +- Header row: 40px, 13px medium, `--ui-fg-faint`, and a 1px `--ui-divider` + bottom border. Fill: `rgb(0 0 0 / 2%)` in light mode, `--ui-panel` in dark + mode (the same as the header bar and the card outer box). +- Dark mode rows use the card well color + (`color-mix(in srgb, var(--ui-panel) 66%, black)`, the same as + `--ui-well-bg` over the panel), so a table looks like a card: header on + top, content below. - Data rows: about 48px, 10px 16px padding, a 1px divider, and a faint hover fill. Put the row action at the far right. - Toolbar on top (search left; filter, sort, and add right), pagination in the footer. Hide the footer when there is only one page. - Use a table, not cards, for a collection that can grow. +- Grid view tiles of a collection are cards without a well. In dark mode they + are `--ui-panel`, the same as the header bar. ### Status badge @@ -497,13 +514,22 @@ The surface ladder is defined in `resources/css/app.css`. | Token | Light | Dark | Use | |---|---|---|---| -| `--coollabs-canvas` | 97% off-white | 14.6% (tinted, = `--color-app`) | page canvas (kept below card fills so cards lift) | -| `--coollabs-elevated` | 98% neutral | 20.02% neutral | shells and modal headers | -| `--coollabs-base` | white | 22.64% neutral | nested modal bodies and legacy panels | -| `--coollabs-recessed` | 96% neutral | 25.2% neutral | inputs and listboxes (`rgb(255 255 255 / 3%)` inside the dark card well) | -| `--coollabs-fill` | 92.2% neutral | 29.31% neutral | dividers and passive fills | -| `--coollabs-line` | translucent dark | 32% neutral | control borders (`rgb(255 255 255 / 8%)` inside the dark card well) | -| `--coollabs-hairline` | 85.5% neutral | 32% neutral | shell rings (crisp enough to read as a card edge, ~1.5:1) | +| `--coollabs-canvas` | 97% off-white | 18.6% (tinted, = `--color-app`) | page canvas (kept below card fills so cards lift) | +| `--color-panel` | white | 23.2% | topbar, top-level card outer box, table header row | +| `--coollabs-elevated` | 98% neutral | 24% | shells and modal headers | +| `--coollabs-base` | white | 26.6% | nested modal bodies and legacy panels | +| `--coollabs-recessed` | 96% neutral | 29.2% | inputs and listboxes (`rgb(255 255 255 / 3%)` inside the dark card well) | +| `--coollabs-fill` | 92.2% neutral | 33.31% | dividers, passive fills, menu hover | +| `--coollabs-line` | translucent dark | 36% | control borders (`rgb(255 255 255 / 8%)` inside the dark card well) | +| `--coollabs-hairline` | 85.5% neutral | 36% | shell rings (crisp enough to read as a card edge, ~1.6:1) | + +The dark level is fixed: canvas 18.6%, panel 23.2%. Also `--color-surface` +24.02%, `--color-raised` 26.64%, `--color-selected` 30.86%, and `--color-log` +19.91%. All dark surfaces have hue 300 (chroma 0.018, borders 0.012, panel +0.03). Do not make dark mode darker. To change the level, move the full ladder +by the same amount, including the opaque reset values for floating panels in +the card well (`.dark .application-settings-section-body :is(.listbox-panel, +...)`). | `--coollabs-subtle` | 50% neutral | 70.8% neutral | labels and muted titles (light darkened for WCAG AA 4.5:1) | Accent behavior is intentionally theme-aware: @@ -531,11 +557,12 @@ content canvas is the base, the topbar breadcrumb bar lifts off it, and cards lift off the content: - **Content canvas** is the base layer: `bg-app` in dark (deepest, - `--color-app` `oklch(14.6% 0.018 300)`, sRGB 11,9,17), `bg-neutral-50` in light. The + `--color-app` `oklch(18.6% 0.018 300)`, sRGB 20,17,26), `bg-neutral-50` in light. The `
` content area and page body use it. - **Topbar chrome** uses `bg-panel` in dark (`--color-panel` - `oklch(19.2% 0.03 300)`, sRGB 21,16,30, a clear step lighter than the content canvas) - and `bg-white` in light, so the header reads as a separate panel. + `oklch(23.2% 0.03 300)`, sRGB 31,26,41, a clear step lighter than the content canvas) + and `bg-white` in light, so the header reads as a separate panel. In dark + mode, top-level layer cards and table header rows use the same `--color-panel`. - **Desktop sidebar and the topbar brand block** (Coolify name and version) use the content canvas color (`bg-neutral-50` / `dark:bg-app`). The sidebar has a 1px right border, and the brand block has a 1px bottom border. These @@ -544,10 +571,10 @@ lift off the content: solid hairline grey. The mobile drawer keeps `bg-panel` / `bg-white`. Dark surface tokens are exact oklch equivalents of chosen sRGB steps. oklch -lightness compresses toward pure black below ~15%, so do not pick dark values by -round oklch percentages. The dark ladder is `--color-app` 10, -`--coollabs-elevated` 22, `--coollabs-base` 28, `--coollabs-recessed` 34 (sRGB), -which reads as distinct surfaces. +lightness compresses toward pure black below ~15%, so check the rendered sRGB +value. The dark ladder is `--color-app` 20,17,26, `--color-panel` 31,26,41, +`--coollabs-elevated` 32,29,39, `--coollabs-base` 39,36,45, +`--coollabs-recessed` 45,42,52 (sRGB), which reads as distinct surfaces. Temperature: in light mode every panel is **pure neutral gray** (r=g=b). In dark mode, every surface token (`--color-app`, `--color-panel`, `--coollabs-*`, @@ -561,9 +588,16 @@ from the others. The light page canvas uses `bg-neutral-50` (not `bg-gray-50`, which is faintly cool) so it matches the neutral cards and chrome. The only intentional color is the purple/yellow brand accent. - **Layer cards** use the outer box and inset well from section 5. -- **Tables and collection tiles** outside a layer card lift off the content - canvas with `dark:bg-white/[0.05]` plus the crisp `--coollabs-hairline` ring; - in light they are `bg-white` with the ring and the restrained card lift. +- **Tables and collection tiles** outside a layer card use the + `rounded-xl border … dark:bg-white/[0.05]` shell plus the crisp + `--coollabs-hairline` ring; in light they are `bg-white` with the ring and + the restrained card lift. In dark mode these shells are painted like layer + cards. A tile, a separate pagination bar, or an empty box is a card without a + well, so it is `--color-panel`. A table (a shell whose first child is a + `border-b` header row) has a `--color-panel` header row and rows in the well + color (`color-mix(in srgb, var(--color-panel) 66%, black)`). + These rules match the Tailwind classes in `resources/css/app.css`, because + the table shells are copied markup without a shared class. Do not paint the content area with the same `bg-panel` as the topbar, and do not drop card fills below `dark:bg-white/[0.05]`; both make surfaces read as one @@ -781,7 +815,9 @@ replace a manual shell when modifying it instead of creating another variant. Anatomy: - soft outer box: 12px radius, 1px border, 6px padding, faint fill - (`rgb(0 0 0 / 0.75%)` light, `rgb(255 255 255 / 6%)` dark); + (`rgb(0 0 0 / 0.75%)` light). In dark mode a top-level card is + `--color-panel`, the same as the topbar; a nested card keeps + `rgb(255 255 255 / 6%)` over its parent; - small title row on the outer box (13px semibold title, 11px description and header links), no divider below it; - body is an inset well: 8px radius, 1px border, white in light and diff --git a/resources/css/app.css b/resources/css/app.css index f6925c890e..24fd511489 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -1270,23 +1270,23 @@ html:not(.dark) .application-console-shell[data-console-theme="system"] .termina ladder, so every panel shares one temperature (no blue cast). */ /* A very small Coolify purple tint (hue 300) so dark mode is not pure black. The translucent cards, wells, and inputs pick it up too. */ - --color-app: oklch(14.6% 0.018 300); - --color-panel: oklch(19.2% 0.03 300); + --color-app: oklch(18.6% 0.018 300); + --color-panel: oklch(23.2% 0.03 300); --coollabs-canvas: var(--color-app); /* The opaque surfaces (menus, listbox panels, modals, tooltips) get the same tint so they match the tinted canvas. */ - --coollabs-elevated: oklch(20.02% 0.018 300); - --coollabs-recessed: oklch(25.2% 0.018 300); - --coollabs-base: oklch(22.64% 0.018 300); - --coollabs-fill: oklch(29.31% 0.018 300); - --color-surface: oklch(20.02% 0.018 300); - --color-raised: oklch(22.64% 0.018 300); - --color-selected: oklch(26.86% 0.018 300); - --color-log: oklch(15.91% 0.018 300); - --coollabs-line: oklch(32% 0.012 300); - /* Crisped from 26.9% (1.36:1 on canvas) to 32% (1.63:1) so dark cards + --coollabs-elevated: oklch(24% 0.018 300); + --coollabs-recessed: oklch(29.2% 0.018 300); + --coollabs-base: oklch(26.6% 0.018 300); + --coollabs-fill: oklch(33.31% 0.018 300); + --color-surface: oklch(24.02% 0.018 300); + --color-raised: oklch(26.64% 0.018 300); + --color-selected: oklch(30.86% 0.018 300); + --color-log: oklch(19.91% 0.018 300); + --coollabs-line: oklch(36% 0.012 300); + /* Kept about 1.6:1 on the canvas so dark cards and tables show a visible edge instead of blending into the canvas. */ - --coollabs-hairline: oklch(32% 0.012 300); + --coollabs-hairline: oklch(36% 0.012 300); --coollabs-subtle: oklch(70.8% 0 0); /* Neutral (was #a8a8b0 / #7a7a84, both cool-tinted). Sidebar text now shares the same neutral temperature as the content text, so the sidebar no longer @@ -1848,6 +1848,27 @@ html[data-theme="custom"]:not(.dark) :is(.application-settings-section, .applica background: linear-gradient(var(--well-bg), var(--well-bg)), var(--card-underlay); } +/* Dark mode: top-level cards use the topbar color (see DESIGN.md). */ +.dark :is(.application-settings-section, .empty-state):not(:is(.application-settings-section, .application-settings-section-body) *) { + background: var(--color-panel); +} + +/* Dark mode: collection tiles, pagination bars, and tables match the cards. + A tile is a card without a well, so it takes the topbar color. A table (a + shell with a header row) takes the topbar color on the header row and the + box content (well) color on the rows. */ +.dark [class~="rounded-xl"][class~="dark:bg-white/[0.05]"] { + background: var(--color-panel); +} + +.dark [class~="rounded-xl"][class~="dark:bg-white/[0.05]"]:has(> [class~="border-b"]:first-child) { + background: color-mix(in srgb, var(--color-panel) 66%, black); +} + +.dark [class~="rounded-xl"][class~="dark:bg-white/[0.05]"] > [class~="border-b"]:first-child { + background: var(--color-panel); +} + /* Brief accent ring when a settings nav sub-item scrolls a section into view. Use a real border on ::after (not animated multi-layer box-shadow) so the ring is the same weight on every side — box-shadow rings look thicker on @@ -1963,8 +1984,8 @@ html[data-theme="custom"]:not(.dark) :is(.application-settings-section, .applica /* Floating panels in the well must stay opaque: restore the global dark values. */ .dark .application-settings-section-body :is(.listbox-panel, .searchable-listbox-panel, [role="listbox"], [role="menu"]) { - --coollabs-recessed: oklch(25.2% 0.018 300); - --coollabs-line: oklch(32% 0.012 300); + --coollabs-recessed: oklch(29.2% 0.018 300); + --coollabs-line: oklch(36% 0.012 300); } .settings-mobile-header {