/* * 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; } }