494 lines
19 KiB
CSS
494 lines
19 KiB
CSS
/*
|
|
* theme-neumorphism.css
|
|
* ─────────────────────────────────────────────────────────────────────────
|
|
* Shared "soft UI" / neumorphic layer for Forgejo.
|
|
*
|
|
* This file contains no palette of its own — it only defines soft-shadow
|
|
* primitives and restyles components to look extruded or recessed. Import it
|
|
* at the TOP of a theme (right after the base palette import) and then set the
|
|
* --nm-* primitives + colours in that theme's :root.
|
|
*
|
|
* Themes that import this: gruvbox-dark, gruvbox-light, terminal,
|
|
* forgejo-dark, forgejo-light, forgejo-auto.
|
|
*/
|
|
|
|
:root {
|
|
/* Derived primitives — themes override --nm-shadow-* / --nm-radius */
|
|
--nm-bg: var(--color-body, #282828);
|
|
--nm-radius: 14px;
|
|
--nm-radius-sm: 10px;
|
|
--nm-raised: 7px 7px 16px var(--nm-shadow-dark, rgba(0, 0, 0, 0.5)),
|
|
-7px -7px 16px var(--nm-shadow-light, rgba(255, 255, 255, 0.05));
|
|
--nm-raised-sm: 4px 4px 10px var(--nm-shadow-dark, rgba(0, 0, 0, 0.45)),
|
|
-4px -4px 10px var(--nm-shadow-light, rgba(255, 255, 255, 0.04));
|
|
/* Buttons sit between the two — clearly lifted, but not chunky */
|
|
--nm-raised-xs: 2px 2px 5px var(--nm-shadow-dark, rgba(0, 0, 0, 0.4)),
|
|
-2px -2px 5px var(--nm-shadow-light, rgba(255, 255, 255, 0.04));
|
|
--nm-inset: inset 5px 5px 12px var(--nm-shadow-dark, rgba(0, 0, 0, 0.5)),
|
|
inset -5px -5px 12px var(--nm-shadow-light, rgba(255, 255, 255, 0.05));
|
|
--nm-inset-sm: inset 3px 3px 7px var(--nm-shadow-dark, rgba(0, 0, 0, 0.45)),
|
|
inset -3px -3px 7px var(--nm-shadow-light, rgba(255, 255, 255, 0.04));
|
|
--nm-flat: 2px 2px 4px var(--nm-shadow-dark, rgba(0, 0, 0, 0.4)),
|
|
-2px -2px 4px var(--nm-shadow-light, rgba(255, 255, 255, 0.04));
|
|
--nm-divider: var(--nm-divider-color, rgba(255, 255, 255, 0.07));
|
|
--nm-ease: cubic-bezier(0.2, 0.7, 0.3, 1);
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════
|
|
Surfaces — raised soft panels
|
|
═══════════════════════════════════════════════════════════════════════ */
|
|
|
|
.ui.segment,
|
|
.ui.segments,
|
|
.ui.secondary.segment,
|
|
.ui.tertiary.segment,
|
|
.ui.raised.segment,
|
|
.ui.stacked.segment,
|
|
.ui.card,
|
|
.ui.cards > .card,
|
|
.ui.message,
|
|
.repository .repo-header,
|
|
.ui.modal,
|
|
.ui.popup,
|
|
.ui.fluid.popup,
|
|
.ui.dropdown .menu {
|
|
background: var(--color-box-body, var(--color-secondary-bg)) !important;
|
|
border: 1px solid transparent !important;
|
|
border-radius: var(--nm-radius) !important;
|
|
box-shadow: var(--nm-raised) !important;
|
|
}
|
|
|
|
/* Nested / attached pieces are part of a parent panel. Keep them flat so we
|
|
never stack soft shadows (repo summary + tab bar, attached tables, rows
|
|
inside a segments group, ...). One panel = one shadow. */
|
|
.ui.attached.segment,
|
|
.ui.attached.header,
|
|
.ui.attached.table,
|
|
.ui.top.attached.segment,
|
|
.ui.middle.attached.segment,
|
|
.ui.bottom.attached.segment,
|
|
.ui.segments > .ui.segment,
|
|
.ui.segments .ui.segment {
|
|
background: transparent !important;
|
|
border-radius: 0 !important;
|
|
border-color: var(--nm-divider) !important;
|
|
box-shadow: none !important;
|
|
}
|
|
|
|
.ui.card,
|
|
.ui.cards > .card {
|
|
transition: box-shadow 0.2s var(--nm-ease), transform 0.2s var(--nm-ease);
|
|
}
|
|
.ui.card:hover,
|
|
.ui.cards > .card:hover {
|
|
box-shadow: var(--nm-raised), 0 0 0 1px var(--color-primary-alpha-20, transparent) !important;
|
|
}
|
|
|
|
/* Headings inside boxes */
|
|
.ui.segment > .ui.header,
|
|
.ui.attached.header,
|
|
.ui.header.segment {
|
|
background: transparent !important;
|
|
border: none !important;
|
|
box-shadow: none !important;
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════
|
|
Buttons — extruded, pressed state sinks in
|
|
═══════════════════════════════════════════════════════════════════════ */
|
|
|
|
.ui.button,
|
|
.ui.buttons .button {
|
|
background: var(--color-box-body, var(--color-button)) !important;
|
|
color: var(--color-text) !important;
|
|
border: 1px solid transparent !important;
|
|
border-radius: var(--nm-radius-sm) !important;
|
|
box-shadow: var(--nm-raised-xs) !important;
|
|
font-weight: 500 !important;
|
|
text-transform: none !important;
|
|
transition: box-shadow 0.16s var(--nm-ease), color 0.16s var(--nm-ease),
|
|
filter 0.16s var(--nm-ease);
|
|
}
|
|
.ui.button:hover,
|
|
.ui.buttons .button:hover {
|
|
color: var(--color-primary) !important;
|
|
box-shadow: var(--nm-raised-sm) !important;
|
|
}
|
|
.ui.button:active,
|
|
.ui.buttons .button:active {
|
|
box-shadow: var(--nm-inset-sm) !important;
|
|
transform: none !important;
|
|
}
|
|
|
|
.ui.primary.button,
|
|
.ui.primary.buttons .button,
|
|
.ui.primary.label {
|
|
background: linear-gradient(
|
|
145deg,
|
|
var(--color-primary-light-1, var(--color-primary)),
|
|
var(--color-primary)
|
|
) !important;
|
|
color: var(--color-primary-contrast, #101010) !important;
|
|
border: 1px solid transparent !important;
|
|
font-weight: 600 !important;
|
|
box-shadow: var(--nm-raised-xs), 0 0 0 1px var(--color-primary-alpha-20, transparent) !important;
|
|
}
|
|
.ui.primary.button:hover,
|
|
.ui.primary.buttons .button:hover {
|
|
filter: brightness(1.06);
|
|
color: var(--color-primary-contrast, #101010) !important;
|
|
box-shadow: var(--nm-raised-sm), 0 0 0 1px var(--color-primary-alpha-40, transparent) !important;
|
|
}
|
|
.ui.primary.button:active {
|
|
filter: brightness(0.95);
|
|
box-shadow: var(--nm-inset-sm) !important;
|
|
}
|
|
|
|
/* Ghost / basic buttons: no extrusion until hover */
|
|
.ui.basic.button,
|
|
.ui.basic.buttons .button {
|
|
background: transparent !important;
|
|
color: var(--color-text-light, var(--color-text)) !important;
|
|
border: 1px solid var(--nm-divider) !important;
|
|
box-shadow: none !important;
|
|
}
|
|
.ui.basic.button:hover,
|
|
.ui.basic.buttons .button:hover {
|
|
background: transparent !important;
|
|
color: var(--color-primary) !important;
|
|
border-color: var(--color-primary-alpha-40, var(--color-primary)) !important;
|
|
box-shadow: var(--nm-flat) !important;
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════
|
|
Inputs — recessed wells
|
|
═══════════════════════════════════════════════════════════════════════ */
|
|
|
|
input,
|
|
textarea,
|
|
select,
|
|
.ui.input input,
|
|
.ui.form input:not([type="checkbox"]):not([type="radio"]),
|
|
.ui.form textarea,
|
|
.ui.form select,
|
|
.ui.search input,
|
|
.ui.action.input input,
|
|
.CodeMirror {
|
|
background: var(--color-body, var(--nm-bg)) !important;
|
|
color: var(--color-input-text, var(--color-text)) !important;
|
|
border: 1px solid transparent !important;
|
|
border-radius: var(--nm-radius-sm) !important;
|
|
box-shadow: var(--nm-inset-sm) !important;
|
|
}
|
|
input::placeholder,
|
|
textarea::placeholder,
|
|
.ui.input input::placeholder {
|
|
color: var(--color-placeholder-text, var(--color-text-light-3)) !important;
|
|
}
|
|
input:focus,
|
|
textarea:focus,
|
|
select:focus,
|
|
.ui.input input:focus,
|
|
.ui.form input:focus,
|
|
.ui.form textarea:focus,
|
|
.ui.search input:focus {
|
|
border-color: transparent !important;
|
|
box-shadow: var(--nm-inset-sm),
|
|
0 0 0 2px var(--color-primary-alpha-30, transparent) !important;
|
|
outline: none !important;
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════
|
|
Input groups (clone panel, code search, explore search)
|
|
One integrated well instead of separate extruded buttons floating next
|
|
to a recessed input.
|
|
═══════════════════════════════════════════════════════════════════════ */
|
|
|
|
.ui.action.input {
|
|
background: var(--color-box-body, var(--color-secondary-bg)) !important;
|
|
border-radius: var(--nm-radius-sm) !important;
|
|
box-shadow: var(--nm-inset-sm) !important;
|
|
overflow: visible !important;
|
|
align-items: stretch !important;
|
|
padding: 0 !important;
|
|
}
|
|
/* Every segment is transparent on ONE shared rounded surface. Because no
|
|
child paints an opaque rectangle, the outer corners always follow the
|
|
group's radius — a child can never square them off. */
|
|
.ui.action.input > input,
|
|
.ui.action.input > .button,
|
|
.ui.action.input > .buttons .button,
|
|
.ui.action.input > .ui.dropdown,
|
|
.ui.action.input > .ui.selection.dropdown {
|
|
background: transparent !important;
|
|
color: var(--color-text) !important;
|
|
border: none !important;
|
|
border-radius: 0 !important;
|
|
box-shadow: none !important;
|
|
margin: 0 !important;
|
|
align-self: stretch !important;
|
|
}
|
|
.ui.action.input > input {
|
|
flex: 1 1 auto !important;
|
|
min-width: 0 !important;
|
|
}
|
|
.ui.action.input > .ui.dropdown .text,
|
|
.ui.action.input > .ui.dropdown .dropdown.icon,
|
|
.ui.action.input > .ui.dropdown .search.icon,
|
|
.ui.action.input > .ui.dropdown .delete.icon {
|
|
color: var(--color-text) !important;
|
|
opacity: 1 !important;
|
|
}
|
|
/* The search-type selector hugs its label instead of the 10em default. */
|
|
.ui.action.input > .ui.dropdown.selection {
|
|
min-width: 0 !important;
|
|
width: auto !important;
|
|
}
|
|
/* Hairline separators. */
|
|
.ui.action.input > :not(:first-child) {
|
|
border-left: 1px solid var(--nm-divider) !important;
|
|
}
|
|
/* Hover = colour only, never an opaque block. */
|
|
.ui.action.input .button:hover,
|
|
.ui.action.input > .ui.dropdown.selection:hover,
|
|
.ui.action.input > .ui.dropdown.selection:hover .text,
|
|
.ui.action.input > .ui.dropdown.selection:hover .dropdown.icon {
|
|
color: var(--color-primary) !important;
|
|
background: transparent !important;
|
|
}
|
|
.ui.action.input:focus-within {
|
|
box-shadow: var(--nm-inset-sm),
|
|
0 0 0 2px var(--color-primary-alpha-30, transparent) !important;
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════
|
|
Navigation & footer — soft, borderless
|
|
═══════════════════════════════════════════════════════════════════════ */
|
|
|
|
.navbar,
|
|
#navbar,
|
|
nav.navbar,
|
|
.ui.menu.navbar,
|
|
.ui.secondary.menu.navbar {
|
|
background: var(--color-nav-bg, var(--color-body)) !important;
|
|
border: none !important;
|
|
box-shadow: 0 8px 18px -12px var(--nm-shadow-dark, rgba(0, 0, 0, 0.6)) !important;
|
|
}
|
|
|
|
.page-footer,
|
|
footer,
|
|
.ui.page-footer {
|
|
background: var(--color-footer, var(--color-body)) !important;
|
|
border: none !important;
|
|
box-shadow: 0 -8px 18px -14px var(--nm-shadow-dark, rgba(0, 0, 0, 0.6)) !important;
|
|
}
|
|
|
|
.ui.tabular.menu,
|
|
.ui.secondary.pointing.menu {
|
|
border: none !important;
|
|
}
|
|
.ui.tabular.menu .active.item,
|
|
.ui.secondary.pointing.menu .active.item {
|
|
background: transparent !important;
|
|
border-color: var(--color-primary) !important;
|
|
color: var(--color-primary) !important;
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════
|
|
Menus, sidebar, breadcrumbs
|
|
═══════════════════════════════════════════════════════════════════════ */
|
|
|
|
.ui.vertical.menu,
|
|
.ui.fluid.vertical.menu,
|
|
.ui.secondary.vertical.menu,
|
|
.settings .ui.menu {
|
|
background: transparent !important;
|
|
border: none !important;
|
|
box-shadow: none !important;
|
|
}
|
|
.ui.vertical.menu .item {
|
|
border: none !important;
|
|
border-radius: var(--nm-radius-sm) !important;
|
|
color: var(--color-text-light, var(--color-text)) !important;
|
|
transition: box-shadow 0.16s var(--nm-ease), color 0.16s var(--nm-ease);
|
|
}
|
|
.ui.vertical.menu .item:hover {
|
|
box-shadow: var(--nm-raised-xs) !important;
|
|
color: var(--color-text) !important;
|
|
background: transparent !important;
|
|
}
|
|
.ui.vertical.menu .active.item,
|
|
.ui.vertical.menu .item.active {
|
|
background: transparent !important;
|
|
color: var(--color-primary) !important;
|
|
box-shadow: var(--nm-inset-sm) !important;
|
|
}
|
|
|
|
.ui.dropdown .menu > .item,
|
|
.ui.popup .item {
|
|
border: none !important;
|
|
}
|
|
.ui.dropdown .menu > .item:hover,
|
|
.ui.dropdown .menu > .item.active,
|
|
.ui.popup .item:hover {
|
|
background: var(--color-primary-alpha-10, rgba(127, 127, 127, 0.12)) !important;
|
|
color: var(--color-primary) !important;
|
|
border-radius: var(--nm-radius-sm) !important;
|
|
}
|
|
|
|
.ui.divider {
|
|
border-color: var(--nm-divider) !important;
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════
|
|
Tables — one raised sheet, recessed header
|
|
═══════════════════════════════════════════════════════════════════════ */
|
|
|
|
.ui.table {
|
|
background: var(--color-box-body, var(--color-secondary-bg)) !important;
|
|
border: none !important;
|
|
border-radius: var(--nm-radius-sm) !important;
|
|
/* No extrusion — tables usually live inside a raised segment already, and
|
|
stacked shadows are what made list pages look off. */
|
|
box-shadow: none !important;
|
|
}
|
|
.ui.table thead th,
|
|
.ui.table > thead > tr > th {
|
|
background: transparent !important;
|
|
border-color: var(--nm-divider) !important;
|
|
color: var(--color-text-light, var(--color-text)) !important;
|
|
box-shadow: inset 0 -1px 0 var(--nm-divider);
|
|
}
|
|
.ui.table > tbody > tr > td {
|
|
background: transparent !important;
|
|
border-color: var(--nm-divider) !important;
|
|
}
|
|
.ui.table tbody tr:hover td {
|
|
background: var(--color-primary-alpha-10, rgba(127, 127, 127, 0.08)) !important;
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════
|
|
Labels, badges, avatars
|
|
═══════════════════════════════════════════════════════════════════════ */
|
|
|
|
.ui.label,
|
|
.ui.labels .label {
|
|
border: 1px solid transparent !important;
|
|
border-radius: 999px !important;
|
|
/* Flat pills: soft shadows on every badge turn busy lists into noise. */
|
|
box-shadow: none !important;
|
|
}
|
|
.ui.basic.label {
|
|
background: transparent !important;
|
|
box-shadow: none !important;
|
|
border: 1px solid var(--nm-divider) !important;
|
|
}
|
|
|
|
.ui.avatar,
|
|
.avatar,
|
|
img.ui.avatar,
|
|
.repository .small-thumbnail,
|
|
.repo-avatar {
|
|
border: 1px solid var(--nm-divider) !important;
|
|
border-radius: var(--nm-radius-sm) !important;
|
|
box-shadow: none !important;
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════
|
|
Messages — raised with a soft accent edge
|
|
═══════════════════════════════════════════════════════════════════════ */
|
|
|
|
.ui.message {
|
|
box-shadow: var(--nm-raised), inset 4px 0 0 var(--color-primary-alpha-40, transparent) !important;
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════
|
|
Pagination, progress, code, scrollbars
|
|
═══════════════════════════════════════════════════════════════════════ */
|
|
|
|
.ui.pagination.menu {
|
|
background: transparent !important;
|
|
box-shadow: none !important;
|
|
border: none !important;
|
|
}
|
|
.ui.pagination.menu .item {
|
|
border: none !important;
|
|
border-radius: var(--nm-radius-sm) !important;
|
|
box-shadow: none !important;
|
|
}
|
|
.ui.pagination.menu .active.item {
|
|
background: linear-gradient(145deg, var(--color-primary-light-1, var(--color-primary)), var(--color-primary)) !important;
|
|
color: var(--color-primary-contrast, #101010) !important;
|
|
box-shadow: var(--nm-raised-sm) !important;
|
|
}
|
|
|
|
.progress,
|
|
.ui.progress {
|
|
background: var(--color-body) !important;
|
|
border-radius: 999px !important;
|
|
box-shadow: var(--nm-inset-sm) !important;
|
|
}
|
|
.progress .bar,
|
|
.ui.progress .bar {
|
|
background: linear-gradient(90deg, var(--color-primary), var(--color-primary-light-1, var(--color-primary))) !important;
|
|
border-radius: 999px !important;
|
|
}
|
|
|
|
/* Block code: recess the rectangle that holds the code, never the text.
|
|
Inline `code` and the text inside a block stay flat, so an empty line or a
|
|
short snippet never gets its own hugging shadow. */
|
|
pre,
|
|
.code-view {
|
|
border-radius: var(--nm-radius-sm) !important;
|
|
box-shadow: var(--nm-inset-sm) !important;
|
|
}
|
|
code,
|
|
pre code,
|
|
pre > code,
|
|
.highlight code,
|
|
.code-view .lines-code,
|
|
.markup,
|
|
.file-content {
|
|
box-shadow: none !important;
|
|
}
|
|
|
|
* {
|
|
scrollbar-width: thin;
|
|
scrollbar-color: var(--color-secondary, var(--nm-divider)) transparent;
|
|
}
|
|
::-webkit-scrollbar {
|
|
width: 10px;
|
|
height: 10px;
|
|
}
|
|
::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
::-webkit-scrollbar-thumb {
|
|
background: var(--color-secondary, var(--nm-divider));
|
|
border-radius: 999px;
|
|
}
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: var(--color-primary-alpha-40, var(--color-secondary));
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════
|
|
Misc polish
|
|
═══════════════════════════════════════════════════════════════════════ */
|
|
|
|
.flex-item,
|
|
.repository.file.list .file-row {
|
|
border-color: var(--nm-divider) !important;
|
|
}
|
|
:focus-visible {
|
|
outline: 2px solid var(--color-primary) !important;
|
|
outline-offset: 2px !important;
|
|
}
|
|
|
|
/* Respect users who prefer less motion */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.ui.button,
|
|
.ui.card,
|
|
.ui.cards > .card {
|
|
transition: none !important;
|
|
}
|
|
}
|