diff --git a/README.md b/README.md index e44f108..2e4bca0 100644 --- a/README.md +++ b/README.md @@ -2,91 +2,110 @@ > Where I flex my projects, code, and stuff. A place to flex. -FlexHub is the brand wrapped around the operator's self-hosted Forgejo -instance. Same git host, same backing store — different paint job. +FlexHub is a self-hosted git platform with a fully custom paint job — +magenta → blue cyberpunk. Custom theme. Custom homepage. Custom +favicon. Custom hover animations. Custom everything. - **Canonical URL:** (admin / push / clone) - **FlexHub-branded entry:** (public showcase) -Both hostnames hit the same Forgejo container on `nullstone`. Repos, -PATs, SSH keys, issues — all identical. Login sessions are independent -(separate cookies per hostname). +Both hostnames hit the same instance on `nullstone`. Repos, PATs, SSH +keys, issues — all identical. Login sessions are independent (separate +cookies per hostname). -This repository holds the customisations that turn vanilla Forgejo into -FlexHub: the homepage template, the theme CSS, the logo set, and the -deploy notes that wire it all together. +Built and operated by **[s8n](https://git.s8n.ru/s8n)**. + +--- + +## 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 -| Path | Purpose | -| ------------------------------------------------ | ------------------------------------------------------------------------ | -| `templates/home.tmpl` | FlexHub homepage. Wordmark + tagline + 4-card "agent grid" feature list. | -| `public/assets/css/theme-flexhub.css` *(planned)* | The orange/black/white theme. `DEFAULT_THEME = flexhub` in `app.ini`. | -| `public/assets/img/` *(planned)* | Logo + wordmark + favicons (`logo.svg`, `flexhub-wordmark.png`, etc). | -| `docs/DEPLOY.md` | How to roll a change out to the live `nullstone` Forgejo container. | -| `LICENSE` | MIT. | +``` +flexhub/ +├── README.md ← this file +├── LICENSE ← MIT +├── docs/ +│ └── DEPLOY.md ← how to roll changes to nullstone +├── 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 -operator pulls them out of the live Forgejo `custom/` tree. The -authoritative copies live on `nullstone` until then. +Anything in this repo maps 1:1 into the live `custom/` tree on +`nullstone`. See `docs/DEPLOY.md` for the exact push flow. --- -## How it's wired up - -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: +## app.ini snippet ```ini -APP_NAME = FlexHub -DEFAULT_THEME = flexhub -THEMES = forgejo-auto,forgejo-light,forgejo-dark,flexhub,veilor +[ui] +THEMES = monochrome,flexhub-og,flexhub-main +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 -invite-only, registration is disabled. If you've found a real bug in -Forgejo itself, file it upstream at -. If you've found a typo on the -homepage, open an issue here and the operator will get to it. +MIT. See `LICENSE`. -Public read access is intentional: browse repos, clone what's MIT or -AGPL, mirror what you like. That's the "flex" part. - ---- - -## 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. +Built and maintained by **[s8n](https://git.s8n.ru/s8n)**. The flex is +the work. diff --git a/public/assets/css/theme-flexhub-main.css b/public/assets/css/theme-flexhub-main.css new file mode 100644 index 0000000..b6ce1fa --- /dev/null +++ b/public/assets/css/theme-flexhub-main.css @@ -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; +} diff --git a/public/assets/css/theme-flexhub-og.css b/public/assets/css/theme-flexhub-og.css new file mode 100644 index 0000000..b9ddaaf --- /dev/null +++ b/public/assets/css/theme-flexhub-og.css @@ -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; +} diff --git a/public/assets/css/theme-monochrome.css b/public/assets/css/theme-monochrome.css new file mode 100644 index 0000000..496fae5 --- /dev/null +++ b/public/assets/css/theme-monochrome.css @@ -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; +} diff --git a/public/assets/img/apple-touch-icon.png b/public/assets/img/apple-touch-icon.png new file mode 100644 index 0000000..2388356 Binary files /dev/null and b/public/assets/img/apple-touch-icon.png differ diff --git a/public/assets/img/favicon-16.png b/public/assets/img/favicon-16.png new file mode 100644 index 0000000..97aebbf Binary files /dev/null and b/public/assets/img/favicon-16.png differ diff --git a/public/assets/img/favicon-32.png b/public/assets/img/favicon-32.png new file mode 100644 index 0000000..e749805 Binary files /dev/null and b/public/assets/img/favicon-32.png differ diff --git a/public/assets/img/favicon.ico b/public/assets/img/favicon.ico new file mode 100644 index 0000000..934a414 Binary files /dev/null and b/public/assets/img/favicon.ico differ diff --git a/public/assets/img/favicon.png b/public/assets/img/favicon.png new file mode 100644 index 0000000..03a7eb5 Binary files /dev/null and b/public/assets/img/favicon.png differ diff --git a/public/assets/img/favicon.svg b/public/assets/img/favicon.svg new file mode 100644 index 0000000..862cb62 --- /dev/null +++ b/public/assets/img/favicon.svg @@ -0,0 +1 @@ + diff --git a/public/assets/img/flexhub-main-apple-touch.png b/public/assets/img/flexhub-main-apple-touch.png new file mode 100644 index 0000000..2388356 Binary files /dev/null and b/public/assets/img/flexhub-main-apple-touch.png differ diff --git a/public/assets/img/flexhub-main-favicon-16.png b/public/assets/img/flexhub-main-favicon-16.png new file mode 100644 index 0000000..97aebbf Binary files /dev/null and b/public/assets/img/flexhub-main-favicon-16.png differ diff --git a/public/assets/img/flexhub-main-favicon-32.png b/public/assets/img/flexhub-main-favicon-32.png new file mode 100644 index 0000000..e749805 Binary files /dev/null and b/public/assets/img/flexhub-main-favicon-32.png differ diff --git a/public/assets/img/flexhub-main-favicon-512.png b/public/assets/img/flexhub-main-favicon-512.png new file mode 100644 index 0000000..03a7eb5 Binary files /dev/null and b/public/assets/img/flexhub-main-favicon-512.png differ diff --git a/public/assets/img/flexhub-main-favicon.ico b/public/assets/img/flexhub-main-favicon.ico new file mode 100644 index 0000000..934a414 Binary files /dev/null and b/public/assets/img/flexhub-main-favicon.ico differ diff --git a/public/assets/img/flexhub-main-favicon.svg b/public/assets/img/flexhub-main-favicon.svg new file mode 100644 index 0000000..862cb62 --- /dev/null +++ b/public/assets/img/flexhub-main-favicon.svg @@ -0,0 +1 @@ + diff --git a/public/assets/img/flexhub-main-home-wordmark.png b/public/assets/img/flexhub-main-home-wordmark.png new file mode 100644 index 0000000..388fab5 Binary files /dev/null and b/public/assets/img/flexhub-main-home-wordmark.png differ diff --git a/public/assets/img/flexhub-main-nav-wordmark.png b/public/assets/img/flexhub-main-nav-wordmark.png new file mode 100644 index 0000000..7288c7a Binary files /dev/null and b/public/assets/img/flexhub-main-nav-wordmark.png differ diff --git a/public/assets/img/flexhub-wordmark.png b/public/assets/img/flexhub-wordmark.png new file mode 100644 index 0000000..4bedd30 Binary files /dev/null and b/public/assets/img/flexhub-wordmark.png differ diff --git a/public/assets/img/logo.png b/public/assets/img/logo.png new file mode 100644 index 0000000..37ce5b0 Binary files /dev/null and b/public/assets/img/logo.png differ diff --git a/public/assets/img/logo.svg b/public/assets/img/logo.svg new file mode 100644 index 0000000..ec0fbea --- /dev/null +++ b/public/assets/img/logo.svg @@ -0,0 +1,7 @@ + + + + Flex + + Hub + diff --git a/templates/base/footer_content.tmpl b/templates/base/footer_content.tmpl new file mode 100644 index 0000000..b5ca50c --- /dev/null +++ b/templates/base/footer_content.tmpl @@ -0,0 +1,6 @@ + diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl new file mode 100644 index 0000000..1e75cee --- /dev/null +++ b/templates/base/head_navbar.tmpl @@ -0,0 +1,202 @@ +{{$notificationUnreadCount := 0}} +{{if and .IsSigned .NotificationUnreadCount}} + {{$notificationUnreadCount = call .NotificationUnreadCount}} +{{end}} + + diff --git a/templates/home.tmpl b/templates/home.tmpl index abe1093..5bed78a 100644 --- a/templates/home.tmpl +++ b/templates/home.tmpl @@ -6,22 +6,22 @@
- A + A

Hosted, hard, unfiltered

Code on my own metal. No upstream daddy, no rate limits, no policy drift.

- B + B

Off-platform

No GitHub, no GitLab, no shared landlord. Just metal in my house and a domain I own.

- C + C

Your repo, your rules

Push private. Push experimental. Push that thing you'd never put on a public profile.

- D + D

Drop the repo

If you built it, it lives here. No filter, no algorithm, no cold-storage policy.