feat(flexhub-main): ship flexhub-main theme + assets + custom chrome
Some checks are pending
secret-scan / gitleaks (HEAD + history) (push) Waiting to run
secret-scan / detect-secrets (entropy + cross-tool) (push) Waiting to run
secret-scan / summary (push) Blocked by required conditions

flexhub-main is now DEFAULT_THEME. Magenta -> blue cyberpunk paint job
on a #5b6cff periwinkle primary, JetBrains Mono site-wide, pixel-glitch
"FLEXHUB" wordmark with 0.35s chromatic shake on hover, bracket-frame
hover on nav items, panel-fill on the bordered Sign-in pill, gradient
1px under-line. Homepage now ships a 4-column agent grid with constant
gradient stroke, outlined-letter markers, and -3px lift + dual outer
glow on hover.

What's in this commit
- 3 themes (CSS): theme-flexhub-main, theme-flexhub-og, theme-monochrome
- Homepage + navbar + footer templates (image-swap logo, stripped lang switch)
- Full favicon set + apple-touch + .ico + PNG-wrapped SVG (works everywhere)
- Pixel-glitch home wordmark + transparent-bg navbar wordmark
- README rewrite covering current state

English-only locked ([i18n] LANGS = en-US). All credit s8n.
The flex is the work.
This commit is contained in:
s8n 2026-05-11 07:24:58 +01:00
parent 7857fa368a
commit 236d4632c9
24 changed files with 1152 additions and 70 deletions

151
README.md
View file

@ -2,91 +2,110 @@
> Where I flex my projects, code, and stuff. A place to flex. > Where I flex my projects, code, and stuff. A place to flex.
FlexHub is the brand wrapped around the operator's self-hosted Forgejo FlexHub is a self-hosted git platform with a fully custom paint job —
instance. Same git host, same backing store — different paint job. magenta → blue cyberpunk. Custom theme. Custom homepage. Custom
favicon. Custom hover animations. Custom everything.
- **Canonical URL:** <https://git.s8n.ru/> (admin / push / clone) - **Canonical URL:** <https://git.s8n.ru/> (admin / push / clone)
- **FlexHub-branded entry:** <https://flexhub.s8n.ru/> (public showcase) - **FlexHub-branded entry:** <https://flexhub.s8n.ru/> (public showcase)
Both hostnames hit the same Forgejo container on `nullstone`. Repos, Both hostnames hit the same instance on `nullstone`. Repos, PATs, SSH
PATs, SSH keys, issues — all identical. Login sessions are independent keys, issues — all identical. Login sessions are independent (separate
(separate cookies per hostname). cookies per hostname).
This repository holds the customisations that turn vanilla Forgejo into Built and operated by **[s8n](https://git.s8n.ru/s8n)**.
FlexHub: the homepage template, the theme CSS, the logo set, and the
deploy notes that wire it all together. ---
## The flex
FlexHub ships **three** themes:
| Theme | Status | Vibe |
| --------------- | --------------------------------- | ------------------------------------------------------------------- |
| `flexhub-main` | **default** — currently shipping | Magenta → blue gradient. Pixel-glitch wordmark. Bracket-frame nav. |
| `flexhub-og` | available via dropdown | Original orange/black/white. The original "FlexHub" paint. |
| `monochrome` | available via dropdown | Pure black + white. Security/terminal aesthetic. |
### What ships in `flexhub-main`
- **Wordmark** — pixel-glitch "FLEXHUB" PNG with magenta + cyan
chromatic ghosts and a 0.35s shake on navbar hover.
- **Favicon**`FH` square favicon (PNG-wrapped SVG so it works
everywhere), apple-touch-icon, .ico and 16/32px PNGs.
- **Type** — JetBrains Mono everywhere, Major Mono Display for accent
text and the navbar bracket-frame.
- **Hover** — bracket-frame `[ label ]` on all navbar items in
magenta/cyan, panel-fill on the bordered Sign-in pill.
- **Homepage** — pixel-glitch wordmark, gradient subtitle, and a
4-column "agent grid" of cards with constant gradient stroke,
outlined-letter markers, and a -3px lift + dual outer glow on hover.
- **Color** — primary `#5b6cff` periwinkle blue-violet (HSL 234°).
Gradient stops `#d423ff → #6a3aff → #22b8ff`.
### Locale
English-only. Language dropdown removed from the footer.
`[i18n] LANGS = en-US` locked, so `?lang=xx` query is a no-op.
--- ---
## What's in this repo ## What's in this repo
| Path | Purpose | ```
| ------------------------------------------------ | ------------------------------------------------------------------------ | flexhub/
| `templates/home.tmpl` | FlexHub homepage. Wordmark + tagline + 4-card "agent grid" feature list. | ├── README.md ← this file
| `public/assets/css/theme-flexhub.css` *(planned)* | The orange/black/white theme. `DEFAULT_THEME = flexhub` in `app.ini`. | ├── LICENSE ← MIT
| `public/assets/img/` *(planned)* | Logo + wordmark + favicons (`logo.svg`, `flexhub-wordmark.png`, etc). | ├── docs/
| `docs/DEPLOY.md` | How to roll a change out to the live `nullstone` Forgejo container. | │ └── DEPLOY.md ← how to roll changes to nullstone
| `LICENSE` | MIT. | ├── templates/
│ ├── home.tmpl ← FlexHub homepage
│ └── base/
│ ├── head_navbar.tmpl ← navbar with image-swap logo
│ └── footer_content.tmpl ← stripped footer (no lang switch)
├── public/assets/
│ ├── css/
│ │ ├── theme-flexhub-main.css ← default, magenta→blue
│ │ ├── theme-flexhub-og.css ← original orange paint
│ │ └── theme-monochrome.css ← pure black + white
│ └── img/
│ ├── flexhub-main-favicon.{ico,svg,png} ← FH gradient favicons
│ ├── flexhub-main-favicon-16.png
│ ├── flexhub-main-favicon-32.png
│ ├── flexhub-main-favicon-512.png
│ ├── flexhub-main-apple-touch.png
│ ├── flexhub-main-nav-wordmark.png ← transparent-bg navbar logo
│ ├── flexhub-main-home-wordmark.png ← pixel-glitch homepage wordmark
│ ├── favicon.{ico,svg,png} ← active globals (= main copies)
│ ├── apple-touch-icon.png ← active global
│ ├── favicon-{16,32}.png ← active globals
│ ├── flexhub-wordmark.png ← og-theme wordmark
│ └── logo.{png,svg} ← og-theme logo
└── .forgejo/workflows/secret-scan.yml ← gitleaks scan
```
The repo is currently template-only; assets get checked in as the Anything in this repo maps 1:1 into the live `custom/` tree on
operator pulls them out of the live Forgejo `custom/` tree. The `nullstone`. See `docs/DEPLOY.md` for the exact push flow.
authoritative copies live on `nullstone` until then.
--- ---
## How it's wired up ## app.ini snippet
Forgejo loads any file under `custom/` to override the equivalent
built-in. On `nullstone` that directory is bind-mounted from the host:
```
/home/docker/forgejo/data/gitea/ → /var/lib/gitea/ (in-container)
└─ custom/
├─ conf/app.ini
├─ templates/home.tmpl ← from this repo
├─ public/assets/css/... ← from this repo
└─ public/assets/img/... ← from this repo
```
The Forgejo container itself is described by the compose file at
`/opt/docker/forgejo/docker-compose.yml`. Container data + customisations
live under `/home/docker/forgejo/` because the root volume on nullstone
is small.
Key settings in `app.ini` that drive the rebrand:
```ini ```ini
APP_NAME = FlexHub [ui]
DEFAULT_THEME = flexhub THEMES = monochrome,flexhub-og,flexhub-main
THEMES = forgejo-auto,forgejo-light,forgejo-dark,flexhub,veilor DEFAULT_THEME = flexhub-main
[i18n]
LANGS = en-US
NAMES = English
``` ```
The two hostnames are split at the Traefik layer, not Forgejo:
- `git.s8n.ru` — docker-provider router, gated by `no-guest@file` ACL.
- `flexhub.s8n.ru` — file-provider router, **public**, no `no-guest`,
rate-limited login. Service `flexhub-svc` points at `http://forgejo:3000`
directly (avoids the file→docker provider race; see DEPLOY.md).
--- ---
## Contributing ## License
Don't. This is the operator's personal git host — accounts are MIT. See `LICENSE`.
invite-only, registration is disabled. If you've found a real bug in
Forgejo itself, file it upstream at
<https://codeberg.org/forgejo/forgejo>. If you've found a typo on the
homepage, open an issue here and the operator will get to it.
Public read access is intentional: browse repos, clone what's MIT or Built and maintained by **[s8n](https://git.s8n.ru/s8n)**. The flex is
AGPL, mirror what you like. That's the "flex" part. the work.
---
## See also
- **Deploy + update workflow:** [`docs/DEPLOY.md`](docs/DEPLOY.md)
- **Initial Forgejo bring-up runbook (operator-internal):**
`_github/infra/forgejo/DEPLOY.md` in the `ai-lab` repo.
- **ARRFLIX project** references `flexhub.s8n.ru` as a mirror — that's
this instance.

View file

@ -0,0 +1,404 @@
@import url('/assets/css/theme-forgejo-dark.css');
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Major+Mono+Display&display=swap');
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url("/assets/font/inter-var.woff2") format("woff2-variations"),
url("/assets/font/inter-var.woff2") format("woff2");
}
/* Site-wide font override — JetBrains Mono everywhere */
:root {
--fonts-proportional: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
--fonts-monospace: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
}
html, body, .ui, .ui.menu, .ui.menu .item, .ui.button, .ui.input input,
.ui.dropdown, .ui.dropdown .menu > .item, input, textarea, select, button {
font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace !important;
}
:root {
/* FlexHub Main — magenta -> blue gradient on black */
/* Primary = periwinkle blue-violet signal */
--color-primary: #5b6cff;
--color-primary-contrast: #ffffff;
--color-primary-dark-1: #4f5ef3;
--color-primary-dark-2: #3d4fe1;
--color-primary-dark-3: #2b40c9;
--color-primary-dark-4: #2031af;
--color-primary-dark-5: #182694;
--color-primary-dark-6: #111c78;
--color-primary-dark-7: #0c145d;
--color-primary-light-1: #7886ff;
--color-primary-light-2: #99a3ff;
--color-primary-light-3: #b8beff;
--color-primary-light-4: #d2d6ff;
--color-primary-light-5: #e0e3ff;
--color-primary-light-6: #ebedff;
--color-primary-light-7: #f5f6ff;
--color-primary-alpha-10: #5b6cff1a;
--color-primary-alpha-20: #5b6cff33;
--color-primary-alpha-30: #5b6cff4d;
--color-primary-alpha-40: #5b6cff66;
--color-primary-alpha-50: #5b6cff80;
--color-primary-alpha-60: #5b6cff99;
--color-primary-alpha-70: #5b6cffb3;
--color-primary-alpha-80: #5b6cffcc;
--color-primary-alpha-90: #5b6cffe6;
--color-primary-hover: var(--color-primary-light-1);
--color-primary-active: var(--color-primary-dark-1);
/* Accent gradient stops */
--fh-grad-from: #d423ff;
--fh-grad-mid: #6a3aff;
--fh-grad-to: #22b8ff;
--fh-grad: linear-gradient(90deg, var(--fh-grad-from) 0%, var(--fh-grad-mid) 50%, var(--fh-grad-to) 100%);
/* Surfaces — pure black base */
--color-body: #000000;
--color-box-header: #0a0a0a;
--color-box-body: #060606;
--color-box-body-highlight: #101010;
--color-card: #0a0a0a;
--color-menu: #060606;
--color-header-wrapper: #000000;
--color-nav-bg: #000000;
--color-secondary-nav-bg: var(--color-body);
--color-footer: #000000;
--color-button: #1a1a1a;
--color-secondary-bg: #1a1a1a;
--color-secondary: #1a1a1a;
--color-secondary-dark-1: #222222;
--color-secondary-dark-2: #2a2a2a;
}
/* FlexHub homepage hero — L1 layout (4-in-row) */
.flexhub-home {
padding: 5rem 1rem 3rem;
text-align: center;
}
.flexhub-home .flexhub-text-wordmark {
display: block;
width: min(640px, 80vw);
aspect-ratio: 1400 / 310;
margin: 0 auto 1.5rem;
background: url('/assets/img/flexhub-main-home-wordmark.png') center/contain no-repeat;
/* hide any text children inherited from template */
font-size: 0;
line-height: 0;
color: transparent;
}
.flexhub-home .flexhub-text-wordmark .fh-flex,
.flexhub-home .flexhub-text-wordmark::after {
display: none !important;
content: none !important;
}
.flexhub-home .tagline {
font-size: clamp(1.1rem, 2vw, 1.4rem);
color: var(--color-text-light);
margin: 0 0 0.4rem;
font-weight: 400;
}
.flexhub-home .subtagline {
font-size: 1rem;
color: var(--color-text-light-2);
margin: 0 0 3rem;
font-style: italic;
background: var(--fh-grad);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
display: inline-block;
}
.flexhub-home .agent-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 1.25rem;
max-width: 1200px;
margin: 0 auto;
padding: 0 2rem 4rem;
}
@media (max-width: 980px) {
.flexhub-home .agent-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* V2 — neon outline (constant gradient stroke + dual glow on hover) */
.flexhub-home .agent-card {
position: relative;
padding: 18px 18px 20px;
border-radius: 14px;
background:
linear-gradient(#0a0612, #0a0612) padding-box,
var(--fh-grad) border-box;
border: 1px solid transparent;
text-align: left;
transition: transform .25s ease, box-shadow .25s ease;
}
.flexhub-home .agent-card:hover {
transform: translateY(-3px);
box-shadow:
0 8px 28px -8px #d423ff66,
0 8px 28px -8px #22b8ff55;
}
/* Unified outlined-letter marker */
.flexhub-home .agent-card .marker {
display: inline-flex;
align-items: center;
justify-content: center;
width: 38px;
height: 38px;
border-radius: 9px;
background:
linear-gradient(#0a0612, #0a0612) padding-box,
linear-gradient(180deg, var(--fh-grad-from), var(--fh-grad-mid), var(--fh-grad-to)) border-box;
border: 1.5px solid transparent;
font-family: "JetBrains Mono", ui-monospace, monospace;
font-size: 17px;
font-weight: 700;
line-height: 1;
margin-bottom: 14px;
transition: filter .25s ease;
}
.flexhub-home .agent-card .marker > span {
background: linear-gradient(180deg, var(--fh-grad-from), var(--fh-grad-mid), var(--fh-grad-to));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
font-weight: 700;
font-family: "JetBrains Mono", ui-monospace, monospace;
}
.flexhub-home .agent-card:hover .marker {
filter: drop-shadow(0 0 6px #d423ff66) drop-shadow(0 0 10px #22b8ff44);
}
.flexhub-home .agent-card h3 {
margin: 0 0 0.4rem;
color: #ffffff;
font-size: 1.05rem;
font-weight: 700;
}
.flexhub-home .agent-card p {
margin: 0;
color: var(--color-text-light);
font-size: 0.92rem;
line-height: 1.45;
}
/* Kill default menu-item hover bg on the logo link */
#navbar-logo,
#navbar-logo:hover,
#navbar-logo:focus,
#navbar-logo.active,
.navbar-left .item#navbar-logo:hover {
background: transparent !important;
box-shadow: none !important;
}
/* Bracket-frame hover on navbar items except logo + sign-in */
#navbar .item:not(#navbar-logo):not([href*="/user/login"]) {
position: relative;
padding-left: 18px !important;
padding-right: 18px !important;
background: transparent !important;
transition: color .2s ease, background .2s ease, border-color .2s ease;
}
#navbar .item:not(#navbar-logo):not([href*="/user/login"])::before,
#navbar .item:not(#navbar-logo):not([href*="/user/login"])::after {
font-family: "Major Mono Display", "JetBrains Mono", monospace !important;
font-weight: 400 !important;
display: inline-block !important;
visibility: visible !important;
position: absolute !important;
top: 50% !important;
transform: translateY(-50%) translateX(0);
opacity: 0;
font-size: 16px !important;
line-height: 1 !important;
width: auto !important;
min-width: 8px !important;
height: auto !important;
margin: 0 !important;
padding: 0 !important;
text-indent: 0 !important;
background: transparent !important;
background-color: transparent !important;
border: 0 !important;
box-shadow: none !important;
overflow: visible !important;
transition: opacity .2s ease, transform .25s cubic-bezier(.2,.7,.1,1.2);
pointer-events: none;
z-index: 2 !important;
}
#navbar .item:not(#navbar-logo):not([href*="/user/login"])::before {
content: "[" !important;
left: 4px !important;
right: auto !important;
color: var(--fh-grad-from) !important;
}
#navbar .item:not(#navbar-logo):not([href*="/user/login"])::after {
content: "]" !important;
right: 4px !important;
left: auto !important;
color: var(--fh-grad-to) !important;
}
/* Defeat Semantic UI's .ui.secondary.menu .item:before { display:none } and
.ui.menu .item:before { width:1px; background:rgba(...) } divider rule. */
.full.height #navbar .item:not(#navbar-logo):not([href*="/user/login"])::before,
.ui.secondary.menu#navbar .item:not(#navbar-logo):not([href*="/user/login"])::before,
.ui.menu#navbar .item:not(#navbar-logo):not([href*="/user/login"])::before {
content: "[" !important;
display: inline-block !important;
visibility: visible !important;
width: auto !important;
height: auto !important;
background: transparent !important;
background-color: transparent !important;
color: var(--fh-grad-from) !important;
left: 4px !important;
right: auto !important;
top: 50% !important;
font-size: 16px !important;
font-family: "Major Mono Display","JetBrains Mono",monospace !important;
}
#navbar .item:not(#navbar-logo):not([href*="/user/login"]):hover,
#navbar .item:not(#navbar-logo):not([href*="/user/login"]).active {
color: #ffffff !important;
background: transparent !important;
box-shadow: none !important;
border-color: transparent !important;
}
#navbar .item:not(#navbar-logo):not([href*="/user/login"]):hover::before,
#navbar .item:not(#navbar-logo):not([href*="/user/login"]).active::before { opacity: 1; transform: translateY(-50%) translateX(-4px); }
#navbar .item:not(#navbar-logo):not([href*="/user/login"]):hover::after,
#navbar .item:not(#navbar-logo):not([href*="/user/login"]).active::after { opacity: 1; transform: translateY(-50%) translateX( 4px); }
/* Sign-in — bordered pill + variant-D panel-fill hover + [← arrow text */
#navbar .item[href*="/user/login"] {
position: relative;
display: inline-flex !important;
align-items: center !important;
gap: 6px !important;
padding: 6px 12px !important;
margin: 0 4px;
border: 1px solid #ffffff22 !important;
border-radius: 6px !important;
color: #e9e6f7 !important;
background-color: transparent !important;
background-image: linear-gradient(90deg, #d423ff26, #22b8ff26) !important;
background-size: 0% 100% !important;
background-position: left center !important;
background-repeat: no-repeat !important;
overflow: hidden;
transition: background-size .35s cubic-bezier(.2,.7,.1,1), border-color .2s, color .2s;
}
#navbar .item[href*="/user/login"]:hover {
background-size: 100% 100% !important;
border-color: #ffffff14 !important;
color: #ffffff !important;
}
/* hide built-in octicon, replace with [← in Major Mono */
#navbar .item[href*="/user/login"] .svg.octicon-sign-in,
#navbar .item[href*="/user/login"] svg.octicon-sign-in,
#navbar .item[href*="/user/login"] > svg { display: none !important; }
#navbar .item[href*="/user/login"]::before {
content: "[←" !important;
display: inline-block !important;
visibility: visible !important;
font-family: "Major Mono Display","JetBrains Mono",monospace !important;
font-weight: 400 !important;
color: #9d97b8 !important;
font-size: 14px !important;
line-height: 1 !important;
position: static !important;
opacity: 1 !important;
transform: none !important;
width: auto !important;
height: auto !important;
margin: 0 !important;
padding: 0 !important;
text-indent: 0 !important;
background: transparent !important;
background-color: transparent !important;
border: 0 !important;
box-shadow: none !important;
pointer-events: none;
transition: color .2s ease;
}
#navbar .item[href*="/user/login"]:hover::before {
color: #ffffff !important;
}
/* gradient 1px under-line on hover */
#navbar .item[href*="/user/login"]::after {
content: "" !important;
position: absolute !important;
left: 0; right: 0; bottom: 0;
height: 1px !important;
width: auto !important;
top: auto !important;
background: var(--fh-grad) !important;
transform: scaleX(0);
transform-origin: left center;
transition: transform .35s cubic-bezier(.2,.7,.1,1);
opacity: 1;
font-size: 0 !important;
border: 0 !important;
pointer-events: none;
}
#navbar .item[href*="/user/login"]:hover::after {
transform: scaleX(1);
}
/* Navbar wordmark — image swap + chromatic-glitch hover */
.flexhub-nav-wordmark {
display: inline-block;
width: 86px;
height: 22px;
background: url('/assets/img/flexhub-main-nav-wordmark.png') left center/contain no-repeat;
font-size: 0;
line-height: 0;
color: transparent;
position: relative;
isolation: isolate;
}
.flexhub-nav-wordmark .fh-flex {
display: none !important;
}
.flexhub-nav-wordmark::before,
.flexhub-nav-wordmark::after {
content: "";
position: absolute;
inset: 0;
background: url('/assets/img/flexhub-main-nav-wordmark.png') left center/contain no-repeat;
mix-blend-mode: screen;
opacity: 0;
pointer-events: none;
transition: opacity .12s linear, transform .12s linear;
}
.flexhub-nav-wordmark::before { filter: hue-rotate(-30deg) saturate(1.4); }
.flexhub-nav-wordmark::after { filter: hue-rotate( 60deg) saturate(1.4); }
.flexhub-nav-wordmark:hover { animation: fhGlitch .35s steps(6, end) 1; }
.flexhub-nav-wordmark:hover::before { opacity: .9; transform: translate(-2px, 1px); }
.flexhub-nav-wordmark:hover::after { opacity: .9; transform: translate( 2px, -1px); }
@keyframes fhGlitch {
0% { transform: translate(0, 0); }
20% { transform: translate(-1px, 0); }
40% { transform: translate( 1px, 0); }
60% { transform: translate(-1px, 1px) skewX(-2deg); }
80% { transform: translate( 1px,-1px) skewX( 2deg); }
100% { transform: translate(0, 0); }
}
/* Footer s8n.ru link in gradient */
footer .footer-content a[href*="s8n.ru"],
.page-footer a[href*="s8n.ru"] {
background: var(--fh-grad);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
font-weight: 700;
}

View file

@ -0,0 +1,320 @@
@import url('/assets/css/theme-forgejo-dark.css');
@font-face {
font-family: "Inter";
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url("/assets/font/inter-var.woff2") format("woff2-variations"),
url("/assets/font/inter-var.woff2") format("woff2");
}
:root {
/* FlexHub — orange/black/white wordmark vibe */
/* Primary = signal orange */
--color-primary: #ff9000;
--color-primary-contrast: #000000;
--color-primary-dark-1: #f08400;
--color-primary-dark-2: #db7800;
--color-primary-dark-3: #c66c00;
--color-primary-dark-4: #b16100;
--color-primary-dark-5: #9c5500;
--color-primary-dark-6: #874900;
--color-primary-dark-7: #723e00;
--color-primary-light-1: #ffa12a;
--color-primary-light-2: #ffb255;
--color-primary-light-3: #ffc37f;
--color-primary-light-4: #ffd4a9;
--color-primary-light-5: #ffe5d4;
--color-primary-light-6: #fff2e6;
--color-primary-light-7: #fff8f0;
--color-primary-alpha-10: #ff90001a;
--color-primary-alpha-20: #ff900033;
--color-primary-alpha-30: #ff90004d;
--color-primary-alpha-40: #ff900066;
--color-primary-alpha-50: #ff900080;
--color-primary-alpha-60: #ff900099;
--color-primary-alpha-70: #ff9000b3;
--color-primary-alpha-80: #ff9000cc;
--color-primary-alpha-90: #ff9000e6;
--color-primary-hover: var(--color-primary-light-1);
--color-primary-active: var(--color-primary-dark-1);
/* Surfaces — pure black base, slight lift on cards */
--color-body: #000000;
--color-box-header: #0a0a0a;
--color-box-body: #060606;
--color-box-body-highlight: #101010;
--color-card: #0a0a0a;
--color-menu: #060606;
--color-header-wrapper: #000000;
--color-nav-bg: #000000;
--color-secondary-nav-bg: var(--color-body);
--color-footer: #000000;
--color-button: #1a1a1a;
--color-secondary-bg: #1a1a1a;
--color-secondary: #1a1a1a;
--color-secondary-dark-1: #222222;
--color-secondary-dark-2: #2a2a2a;
/* Borders — subtle orange tint on hover */
--color-input-border: #2a2a2a;
--color-input-border-hover: var(--color-primary);
--color-light-border: #ffffff1a;
/* Inputs / search box — pure black, not blue-grey */
--color-input-background: #000000;
--color-input-toggle-background: #000000;
--color-input-text: #f0f0f0;
--color-form-background: #000000;
--color-active-line: var(--color-primary);
/* Accent */
--color-accent: var(--color-primary);
--color-small-accent: var(--color-primary-light-2);
--color-highlight-fg: var(--color-primary);
--color-highlight-bg: var(--color-primary-alpha-10);
/* Text + focus */
--color-text: #f0f0f0;
--color-text-light: #c8c8c8;
--color-text-light-1: #b0b0b0;
--color-text-light-2: #909090;
--color-text-light-3: #707070;
--color-text-focus: var(--color-primary);
/* Reaction / overlays */
--color-reaction-bg: var(--color-primary-alpha-10);
--color-reaction-active-bg: var(--color-primary-alpha-30);
--color-reaction-hover-bg: var(--color-primary-alpha-40);
--color-overlay-backdrop: #000000d0;
/* Links */
--color-link: var(--color-primary);
--color-link-hover: var(--color-primary-light-1);
}
::selection {
background: #ff9000 !important;
color: #000000 !important;
}
/* FlexHub homepage hero */
.flexhub-home {
padding: 4rem 1rem 3rem;
text-align: center;
}
.flexhub-home .flexhub-wordmark {
max-width: min(420px, 70vw);
height: auto;
margin: 0 auto 2rem;
display: block;
}
.flexhub-home .flexhub-text-wordmark {
display: inline-flex;
align-items: center;
font-family: "Inter", system-ui, Helvetica, Arial, sans-serif;
font-weight: 700;
font-size: clamp(3rem, 9vw, 6rem);
line-height: 1;
letter-spacing: -0.04em;
margin: 0 auto 1.25rem;
white-space: nowrap;
}
.flexhub-home .flexhub-text-wordmark .fh-flex {
color: #ffffff;
padding: 0.1em 0.15em 0.1em 0;
}
.flexhub-home .flexhub-text-wordmark::after {
content: "Hub";
color: #000000;
background: var(--color-primary);
padding: 0.05em 0.2em;
border-radius: 0.18em;
}
.flexhub-home h1 {
font-size: clamp(2.2rem, 5vw, 3.4rem);
font-weight: 900;
letter-spacing: -0.02em;
margin: 0 0 0.6rem;
color: #ffffff;
}
.flexhub-home h1 .flex {
color: #ffffff;
}
.flexhub-home h1 .hub {
color: var(--color-primary);
}
.flexhub-home .tagline {
font-size: clamp(1.1rem, 2vw, 1.4rem);
color: var(--color-text-light);
margin: 0 0 0.4rem;
font-weight: 400;
}
.flexhub-home .subtagline {
font-size: 1rem;
color: var(--color-text-light-2);
margin: 0 0 3rem;
font-style: italic;
}
.flexhub-home .agent-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 1.25rem;
max-width: 980px;
margin: 0 auto;
padding: 0 1rem;
}
.flexhub-home .agent-card {
background: var(--color-card);
border: 1px solid var(--color-input-border);
border-radius: 12px;
padding: 1.5rem 1.25rem;
text-align: left;
transition: border-color .15s, transform .15s;
}
.flexhub-home .agent-card:hover {
border-color: var(--color-primary);
transform: translateY(-2px);
}
.flexhub-home .agent-card .marker {
display: inline-block;
width: 28px;
height: 28px;
border-radius: 6px;
background: var(--color-primary);
color: #000;
font-weight: 900;
text-align: center;
line-height: 28px;
font-size: 1rem;
margin-bottom: 0.75rem;
}
.flexhub-home .agent-card h3 {
margin: 0 0 0.4rem;
color: #ffffff;
font-size: 1.05rem;
font-weight: 700;
}
.flexhub-home .agent-card p {
margin: 0;
color: var(--color-text-light);
font-size: 0.92rem;
line-height: 1.45;
}
/* Navbar wordmark — text-rendered via head_navbar.tmpl override */
.flexhub-nav-wordmark {
display: inline-flex;
align-items: center;
font-family: "Inter", system-ui, Helvetica, Arial, sans-serif;
font-weight: 700;
font-size: 22px;
letter-spacing: -0.02em;
line-height: 1;
white-space: nowrap;
}
.flexhub-nav-wordmark .fh-flex {
color: #ffffff;
padding: 4px 6px 4px 2px;
}
.flexhub-nav-wordmark::after {
content: "Hub";
color: #000000;
background: var(--color-primary);
padding: 4px 8px;
border-radius: 6px;
}
/* Footer: orange s8n.ru link + white placeholder text */
.page-footer .footer-placeholder {
color: #ffffff;
margin-right: 0.4rem;
}
.page-footer .left-links a {
color: var(--color-primary);
}
.page-footer .left-links a:hover {
color: var(--color-primary-light-1);
}
/* Top navbar (Explore etc): underlined hyperlink style, transparent bg, kill blue hover chip */
#navbar a.item,
#navbar .item {
background: transparent !important;
border-radius: 0 !important;
}
#navbar a.item:hover,
#navbar .item:hover {
background: transparent !important;
color: var(--color-primary) !important;
}
#navbar a.item.active,
#navbar .item.active {
background: transparent !important;
color: var(--color-primary) !important;
text-decoration: underline;
text-decoration-color: var(--color-primary);
text-decoration-thickness: 2px;
text-underline-offset: 6px;
}
#navbar a.item.active:hover {
color: var(--color-primary-light-1) !important;
}
/* Tab menus (Repositories / Users / Organizations): keep big border-bottom only,
strip the close text-decoration line that doubles up under the label. */
.ui.tabs.menu .item.active,
.ui.tabs.menu .active.item,
.ui.tabular.menu .item.active,
.ui.tabular.menu .active.item,
.ui.secondary.pointing.menu .item.active,
.ui.secondary.pointing.menu .active.item {
text-decoration: none !important;
background: transparent !important;
color: var(--color-primary) !important;
border-color: var(--color-primary) !important;
}
/* Search input + filter/sort dropdowns: pure black */
.ui.input > input,
.ui.action.input > input,
.ui.form input[type="text"],
.ui.form input[type="search"],
.ui.search input,
input[type="text"],
input[type="search"] {
background: #000000 !important;
color: var(--color-input-text) !important;
border-color: var(--color-input-border) !important;
}
.ui.input > input:focus,
input[type="text"]:focus,
input[type="search"]:focus {
background: #000000 !important;
border-color: var(--color-primary) !important;
}
.ui.action.input > .button,
.ui.action.input > .ui.button {
background: #000000 !important;
color: var(--color-text) !important;
border: 1px solid var(--color-input-border) !important;
}
.ui.action.input > .button:hover {
border-color: var(--color-primary) !important;
color: var(--color-primary) !important;
}
/* Primary buttons keep orange-on-black */
.ui.primary.button,
.ui.primary.buttons .button {
background: var(--color-primary) !important;
color: #000 !important;
font-weight: 700;
}
.ui.primary.button:hover,
.ui.primary.buttons .button:hover {
background: var(--color-primary-light-1) !important;
color: #000 !important;
}

View file

@ -0,0 +1,122 @@
@import url('/assets/css/theme-forgejo-dark.css');
:root {
/* Veilor monochrome — security/terminal aesthetic */
/* Steel scale: pure neutral grays (zero hue) */
--steel-900: #0a0a0a;
--steel-850: #0f0f0f;
--steel-800: #131313;
--steel-750: #181818;
--steel-700: #1d1d1d;
--steel-650: #232323;
--steel-600: #2b2b2b;
--steel-550: #353535;
--steel-500: #424242;
--steel-450: #525252;
--steel-400: #636363;
--steel-350: #767676;
--steel-300: #8b8b8b;
--steel-250: #a0a0a0;
--steel-200: #b6b6b6;
--steel-150: #cdcdcd;
--steel-100: #e6e6e6;
/* Primary = stark white accent */
--color-primary: #ffffff;
--color-primary-contrast: #000000;
--color-primary-dark-1: #f0f0f0;
--color-primary-dark-2: #e0e0e0;
--color-primary-dark-3: #d0d0d0;
--color-primary-dark-4: #c0c0c0;
--color-primary-dark-5: #b0b0b0;
--color-primary-dark-6: #a0a0a0;
--color-primary-dark-7: #909090;
--color-primary-light-1: #e6e6e6;
--color-primary-light-2: #cdcdcd;
--color-primary-light-3: #b6b6b6;
--color-primary-light-4: #a0a0a0;
--color-primary-light-5: #8b8b8b;
--color-primary-light-6: #767676;
--color-primary-light-7: #636363;
--color-primary-alpha-10: #ffffff19;
--color-primary-alpha-20: #ffffff33;
--color-primary-alpha-30: #ffffff4b;
--color-primary-alpha-40: #ffffff66;
--color-primary-alpha-50: #ffffff80;
--color-primary-alpha-60: #ffffff99;
--color-primary-alpha-70: #ffffffb3;
--color-primary-alpha-80: #ffffffcc;
--color-primary-alpha-90: #ffffffe1;
--color-primary-hover: var(--color-primary-light-1);
--color-primary-active: var(--color-primary-light-2);
/* Body / surfaces — pitch black base */
--color-body: #08080a;
--color-box-header: #121214;
--color-box-body: #0e0e10;
--color-box-body-highlight: #16161a;
--color-card: #121214;
--color-menu: #0e0e10;
--color-header-wrapper: #050507;
--color-nav-bg: #050507;
--color-secondary-nav-bg: var(--color-body);
--color-footer: #050507;
--color-button: #1d1d1d;
--color-secondary-bg: #1d1d1d;
/* Borders / lines */
--color-input-border: #2a2a2a;
--color-input-border-hover: #404040;
--color-light-border: #ffffff1a;
/* Accent override (chains back to primary) */
--color-accent: #ffffff;
--color-small-accent: #b6b6b6;
--color-highlight-fg: #e6e6e6;
--color-highlight-bg: #ffffff14;
/* Reaction / focus */
--color-text-focus: #ffffff;
--color-reaction-bg: #ffffff10;
--color-reaction-active-bg: #ffffff30;
--color-reaction-hover-bg: #ffffff40;
/* Selection */
--color-overlay-backdrop: #000000d0;
}
::selection {
background: #ffffff !important;
color: #000000 !important;
}
/* Strip color from primary buttons / labels (override late-cascading rules) */
.ui.primary.button,
.ui.primary.buttons .button {
background: var(--color-primary) !important;
color: var(--color-primary-contrast) !important;
}
.ui.primary.button:hover,
.ui.primary.buttons .button:hover {
background: var(--color-primary-dark-2) !important;
}
.ui.primary.label,
.ui.primary.labels .label,
.ui.red.label.notification_count {
background-color: #ffffff !important;
color: #000000 !important;
}
/* Top nav: pure black bar, white text */
.ui.secondary.menu .item,
#navbar .item {
color: var(--color-text) !important;
}
/* Subtle white outline on cards */
.repo-list .item,
.ui.cards > .card,
.ui.card {
border: 1px solid #1f1f22 !important;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
public/assets/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 407 135" width="407" height="135">
<rect width="407" height="135" fill="#000000"/>
<text x="15" y="105" font-family="Inter,system-ui,Helvetica,Arial,sans-serif" font-weight="900" font-size="105" fill="#FFFFFF" letter-spacing="-3">Flex</text>
<rect x="205" y="10" width="190" height="115" rx="14" fill="#FF9000"/>
<text x="222" y="105" font-family="Inter,system-ui,Helvetica,Arial,sans-serif" font-weight="900" font-size="105" fill="#000000" letter-spacing="-3">Hub</text>
</svg>

After

Width:  |  Height:  |  Size: 579 B

View file

@ -0,0 +1,6 @@
<footer class="page-footer" role="group" aria-label="{{ctx.Locale.Tr "aria.footer"}}">
<div class="left-links" role="contentinfo" aria-label="{{ctx.Locale.Tr "aria.footer.software"}}">
<span class="footer-placeholder">built by&nbsp;</span>
<a target="_blank" rel="noopener noreferrer" href="https://s8n.ru">s8n.ru</a>
</div>
</footer>

View file

@ -0,0 +1,202 @@
{{$notificationUnreadCount := 0}}
{{if and .IsSigned .NotificationUnreadCount}}
{{$notificationUnreadCount = call .NotificationUnreadCount}}
{{end}}
<nav id="navbar" aria-label="{{ctx.Locale.Tr "aria.navbar"}}">
<div class="navbar-left ui secondary menu">
<!-- the logo -->
<a class="item" id="navbar-logo" href="{{AppSubUrl}}/" aria-label="{{if .IsSigned}}{{ctx.Locale.Tr "dashboard"}}{{else}}{{ctx.Locale.Tr "home"}}{{end}}">
<span class="flexhub-nav-wordmark" aria-hidden="true"><span class="fh-flex">Flex</span></span>
</a>
<!-- mobile right menu, it must be here because in mobile view, each item is a flex column, the first item is a full row column -->
<div class="ui secondary menu item navbar-mobile-right only-mobile">
{{if .IsSigned}}
<a id="mobile-notifications-icon" class="item tw-w-auto tw-p-2" href="{{AppSubUrl}}/notifications" data-tooltip-content="{{ctx.Locale.Tr "notifications"}}" aria-label="{{ctx.Locale.Tr "notifications"}}">
<div class="tw-relative">
{{svg "octicon-bell"}}
<span class="notification_count{{if not $notificationUnreadCount}} tw-hidden{{end}}">{{$notificationUnreadCount}}</span>
</div>
</a>
{{end}}
<button class="item tw-w-auto ui icon mini button tw-p-2 tw-m-0" id="navbar-expand-toggle" aria-label="{{ctx.Locale.Tr "toggle_menu"}}">{{svg "octicon-three-bars"}}</button>
</div>
<!-- navbar links non-mobile -->
{{if and .IsSigned .MustChangePassword}}
{{/* No links */}}
{{else if .IsSigned}}
{{if not .UnitIssuesGlobalDisabled}}
<a class="item{{if .PageIsIssues}} active{{end}}" href="{{AppSubUrl}}/issues">{{ctx.Locale.Tr "issues"}}</a>
{{end}}
{{if not .UnitPullsGlobalDisabled}}
<a class="item{{if .PageIsPulls}} active{{end}}" href="{{AppSubUrl}}/pulls">{{ctx.Locale.Tr "pull_requests"}}</a>
{{end}}
{{if not (and .UnitIssuesGlobalDisabled .UnitPullsGlobalDisabled)}}
{{if .ShowMilestonesDashboardPage}}
<a class="item{{if .PageIsMilestonesDashboard}} active{{end}}" href="{{AppSubUrl}}/milestones">{{ctx.Locale.Tr "milestones"}}</a>
{{end}}
{{end}}
<a class="item{{if .PageIsExplore}} active{{end}}" href="{{AppSubUrl}}/explore/repos">{{ctx.Locale.Tr "explore"}}</a>
{{else if .IsLandingPageOrganizations}}
<a class="item{{if .PageIsExplore}} active{{end}}" href="{{AppSubUrl}}/explore/organizations">{{ctx.Locale.Tr "explore"}}</a>
{{else}}
<a class="item{{if .PageIsExplore}} active{{end}}" href="{{AppSubUrl}}/explore/repos">{{ctx.Locale.Tr "explore"}}</a>
{{end}}
{{template "custom/extra_links" .}}
</div>
<!-- the full dropdown menus -->
<div class="navbar-right ui secondary menu">
{{if and .IsSigned .MustChangePassword}}
<div class="ui dropdown jump item" data-tooltip-content="{{ctx.Locale.Tr "user_profile_and_more"}}">
<span class="text tw-flex tw-items-center">
{{ctx.AvatarUtils.Avatar .SignedUser 24 "tw-mr-1"}}
<span class="only-mobile tw-ml-2">{{.SignedUser.Name}}</span>
<span class="not-mobile">{{svg "octicon-triangle-down"}}</span>
</span>
<div class="menu user-menu">
<div class="ui header">
{{ctx.Locale.Tr "signed_in_as"}} <strong>{{.SignedUser.Name}}</strong>
</div>
<div class="divider"></div>
<a class="item link-action" href data-url="{{AppSubUrl}}/user/logout">
{{svg "octicon-sign-out"}}
{{ctx.Locale.Tr "sign_out"}}
</a>
</div><!-- end content avatar menu -->
</div><!-- end dropdown avatar menu -->
{{else if .IsSigned}}
{{if EnableTimetracking}}
<a class="active-stopwatch-trigger item tw-mx-0{{if not .ActiveStopwatch}} tw-hidden{{end}}" href="{{.ActiveStopwatch.IssueLink}}" title="{{ctx.Locale.Tr "active_stopwatch"}}">
<div class="tw-relative">
{{svg "octicon-stopwatch"}}
<span class="header-stopwatch-dot"></span>
</div>
<span class="only-mobile tw-ml-2">{{ctx.Locale.Tr "active_stopwatch"}}</span>
</a>
<div class="active-stopwatch-popup item tippy-target tw-p-2">
<div class="tw-flex tw-items-center">
<a class="stopwatch-link tw-flex tw-items-center" href="{{.ActiveStopwatch.IssueLink}}">
{{svg "octicon-issue-opened" 16 "tw-mr-2"}}
<span class="stopwatch-issue">{{.ActiveStopwatch.RepoSlug}}#{{.ActiveStopwatch.IssueIndex}}</span>
<span class="ui primary label stopwatch-time tw-my-0 tw-mx-4" data-seconds="{{.ActiveStopwatch.Seconds}}">
{{if .ActiveStopwatch}}{{Sec2Time .ActiveStopwatch.Seconds}}{{end}}
</span>
</a>
<form class="stopwatch-commit" method="post" action="{{.ActiveStopwatch.IssueLink}}/times/stopwatch/toggle">
{{.CsrfTokenHtml}}
<button
type="submit"
class="ui button mini compact basic icon"
data-tooltip-content="{{ctx.Locale.Tr "repo.issues.stop_tracking"}}"
>{{svg "octicon-square-fill"}}</button>
</form>
<form class="stopwatch-cancel" method="post" action="{{.ActiveStopwatch.IssueLink}}/times/stopwatch/cancel">
{{.CsrfTokenHtml}}
<button
type="submit"
class="ui button mini compact basic icon"
data-tooltip-content="{{ctx.Locale.Tr "repo.issues.cancel_tracking"}}"
>{{svg "octicon-trash"}}</button>
</form>
</div>
</div>
{{end}}
<a class="item not-mobile tw-mx-0" href="{{AppSubUrl}}/notifications" data-tooltip-content="{{ctx.Locale.Tr "notifications"}}" aria-label="{{ctx.Locale.Tr "notifications"}}">
<div class="tw-relative">
{{svg "octicon-bell"}}
<span class="notification_count{{if not $notificationUnreadCount}} tw-hidden{{end}}">{{$notificationUnreadCount}}</span>
</div>
</a>
<div class="ui dropdown jump item tw-mx-0 tw-pr-2" data-tooltip-content="{{ctx.Locale.Tr "create_new"}}">
<span class="text">
{{svg "octicon-plus"}}
<span class="not-mobile">{{svg "octicon-triangle-down"}}</span>
<span class="only-mobile">{{ctx.Locale.Tr "create_new"}}</span>
</span>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/repo/create">
{{svg "octicon-plus"}} {{ctx.Locale.Tr "new_repo.link"}}
</a>
{{if not .DisableMigrations}}
<a class="item" href="{{AppSubUrl}}/repo/migrate">
{{svg "octicon-repo-push"}} {{ctx.Locale.Tr "new_migrate.link"}}
</a>
{{end}}
{{if .SignedUser.CanCreateOrganization}}
<a class="item" href="{{AppSubUrl}}/org/create">
{{svg "octicon-organization"}} {{ctx.Locale.Tr "new_org.link"}}
</a>
{{end}}
</div><!-- end content create new menu -->
</div><!-- end dropdown menu create new -->
<div class="ui dropdown jump item tw-mx-0 tw-pr-2" data-tooltip-content="{{ctx.Locale.Tr "user_profile_and_more"}}">
<span class="text tw-flex tw-items-center">
{{ctx.AvatarUtils.Avatar .SignedUser 24 "tw-mr-1"}}
<span class="only-mobile tw-ml-2">{{.SignedUser.Name}}</span>
<span class="not-mobile">{{svg "octicon-triangle-down"}}</span>
</span>
<div class="menu user-menu">
<div class="ui header">
{{ctx.Locale.Tr "signed_in_as"}} <strong>{{.SignedUser.Name}}</strong>
</div>
<div class="divider"></div>
<a class="item" href="{{.SignedUser.HomeLink}}">
{{svg "octicon-person"}}
{{ctx.Locale.Tr "your_profile"}}
</a>
{{if not .DisableStars}}
<a class="item" href="{{.SignedUser.HomeLink}}?tab=stars">
{{svg "octicon-star"}}
{{ctx.Locale.Tr "your_starred"}}
</a>
{{end}}
<a class="item" href="{{AppSubUrl}}/notifications/subscriptions">
{{svg "octicon-bell"}}
{{ctx.Locale.Tr "notification.subscriptions"}}
</a>
<a class="{{if .PageIsUserSettings}}active {{end}}item" href="{{AppSubUrl}}/user/settings">
{{svg "octicon-tools"}}
{{ctx.Locale.Tr "your_settings"}}
</a>
<a class="item" target="_blank" rel="noopener noreferrer" href="https://forgejo.org/docs/latest/">
{{svg "octicon-question"}}
{{ctx.Locale.Tr "help"}}
</a>
{{if .IsAdmin}}
<div class="divider"></div>
<a class="{{if .PageIsAdmin}}active {{end}}item" href="{{AppSubUrl}}/admin">
{{svg "octicon-server"}}
{{ctx.Locale.Tr "admin_panel"}}
</a>
{{end}}
<div class="divider"></div>
<a class="item link-action" href data-url="{{AppSubUrl}}/user/logout">
{{svg "octicon-sign-out"}}
{{ctx.Locale.Tr "sign_out"}}
</a>
</div><!-- end content avatar menu -->
</div><!-- end dropdown avatar menu -->
{{else}}
{{if .ShowRegistrationButton}}
<a class="item{{if .PageIsSignUp}} active{{end}}" href="{{AppSubUrl}}/user/sign_up">
{{svg "octicon-person"}} {{ctx.Locale.Tr "register"}}
</a>
{{end}}
<a class="item{{if .PageIsSignIn}} active{{end}}" rel="nofollow" href="{{AppSubUrl}}/user/login{{if not .PageIsSignIn}}?redirect_to={{.CurrentURL}}{{end}}">
{{svg "octicon-sign-in"}} {{ctx.Locale.Tr "sign_in"}}
</a>
{{end}}
</div><!-- end full right menu -->
</nav>

View file

@ -6,22 +6,22 @@
<div class="agent-grid"> <div class="agent-grid">
<div class="agent-card"> <div class="agent-card">
<span class="marker">A</span> <span class="marker"><span>A</span></span>
<h3>Hosted, hard, unfiltered</h3> <h3>Hosted, hard, unfiltered</h3>
<p>Code on my own metal. No upstream daddy, no rate limits, no policy drift.</p> <p>Code on my own metal. No upstream daddy, no rate limits, no policy drift.</p>
</div> </div>
<div class="agent-card"> <div class="agent-card">
<span class="marker">B</span> <span class="marker"><span>B</span></span>
<h3>Off-platform</h3> <h3>Off-platform</h3>
<p>No GitHub, no GitLab, no shared landlord. Just metal in my house and a domain I own.</p> <p>No GitHub, no GitLab, no shared landlord. Just metal in my house and a domain I own.</p>
</div> </div>
<div class="agent-card"> <div class="agent-card">
<span class="marker">C</span> <span class="marker"><span>C</span></span>
<h3>Your repo, your rules</h3> <h3>Your repo, your rules</h3>
<p>Push private. Push experimental. Push that thing you'd never put on a public profile.</p> <p>Push private. Push experimental. Push that thing you'd never put on a public profile.</p>
</div> </div>
<div class="agent-card"> <div class="agent-card">
<span class="marker">D</span> <span class="marker"><span>D</span></span>
<h3>Drop the repo</h3> <h3>Drop the repo</h3>
<p>If you built it, it lives here. No filter, no algorithm, no cold-storage policy.</p> <p>If you built it, it lives here. No filter, no algorithm, no cold-storage policy.</p>
</div> </div>