Add Forgejo theme and homepage

This commit is contained in:
Martin Dvořák 2026-10-22 21:07:24 +00:00
commit 92363fca9b
31 changed files with 4044 additions and 0 deletions

View file

@ -0,0 +1,494 @@
/*
* 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;
}
}