├── .cocorc
├── .design
└── logos.ai
├── .eslintrc.json
├── .github
└── workflows
│ └── release.yml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .vscode
└── settings.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── figs
├── issue.png
├── pr.png
├── repo.png
└── settings.png
├── package-lock.json
├── package.json
├── src
└── themes
│ ├── img
│ ├── favicon.svg
│ ├── loading.svg
│ └── logo.svg
│ └── scss
│ ├── _utils
│ └── _color-utils.scss
│ ├── _vars.scss
│ ├── auto.scss
│ ├── dark.scss
│ ├── light.scss
│ └── theme
│ ├── components
│ ├── _avatar.scss
│ ├── _boxes.scss
│ ├── _editor.scss
│ ├── _file-content.scss
│ ├── _forms.scss
│ ├── _labels.scss
│ ├── _markup.scss
│ ├── button
│ │ └── index.scss
│ ├── forms
│ │ ├── _input.scss
│ │ ├── _menu.scss
│ │ └── index.scss
│ └── index.scss
│ ├── index.scss
│ ├── modules
│ ├── _chroma.scss
│ ├── _codemirror.scss
│ ├── _monaco.scss
│ ├── index.scss
│ ├── issues
│ │ ├── _issue-sidebar.scss
│ │ ├── _issue.scss
│ │ ├── _list.scss
│ │ └── index.scss
│ ├── repo
│ │ ├── _file-list.scss
│ │ ├── _home.scss
│ │ ├── _secondary-navbar.scss
│ │ └── index.scss
│ └── settings
│ │ ├── _pages.scss
│ │ ├── index.scss
│ │ └── section
│ │ ├── _content.scss
│ │ ├── _nav.scss
│ │ └── index.scss
│ └── vars
│ ├── _base.scss
│ ├── _colors.scss
│ └── index.scss
├── tools
├── build.js
├── deploy.js
├── minimize-css.js
├── restart.js
├── serve.js
├── tasks
│ ├── copy-to.js
│ ├── deploy.js
│ ├── fonts.js
│ ├── img.js
│ ├── optimize-css.js
│ ├── restart-service.js
│ ├── scss.js
│ └── templates.js
└── utils
│ ├── funcs.js
│ ├── logger.js
│ └── task-debouncer.js
└── 🍵 lugit-theme.code-workspace
/.cocorc:
--------------------------------------------------------------------------------
1 | useEmoji: true
2 | askScope: false
--------------------------------------------------------------------------------
/.design/logos.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucas-labs/gitea-lugit-theme/981d64f1998fb0f25186560f4907b45979b29f64/.design/logos.ai
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "node": true,
4 | "es2021": true
5 | },
6 | "extends": "eslint:recommended",
7 | "parserOptions": {
8 | "ecmaVersion": "latest",
9 | "sourceType": "module"
10 | },
11 | "rules": {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 | on:
3 | push:
4 | tags:
5 | - "v*"
6 |
7 | jobs:
8 | release:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v4
12 | - uses: actions/setup-node@v4
13 | with:
14 | node-version: 20
15 | - run: npm ci
16 | - run: npm run build
17 | - name: Create tarball
18 | run: tar czf gitea-lugit-theme.tar.gz --directory=./dist .
19 | - name: Add zips to release
20 | uses: softprops/action-gh-release@v2
21 | with:
22 | files: ./gitea-lugit-theme.tar.gz
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Node ###
2 | # Logs
3 | logs
4 | *.log
5 | npm-debug.log*
6 | yarn-debug.log*
7 | yarn-error.log*
8 | lerna-debug.log*
9 | .pnpm-debug.log*
10 |
11 | # Diagnostic reports (https://nodejs.org/api/report.html)
12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
13 |
14 | # Runtime data
15 | pids
16 | *.pid
17 | *.seed
18 | *.pid.lock
19 |
20 | # Directory for instrumented libs generated by jscoverage/JSCover
21 | lib-cov
22 |
23 | # Coverage directory used by tools like istanbul
24 | coverage
25 | *.lcov
26 |
27 | # nyc test coverage
28 | .nyc_output
29 |
30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
31 | .grunt
32 |
33 | # Bower dependency directory (https://bower.io/)
34 | bower_components
35 |
36 | # node-waf configuration
37 | .lock-wscript
38 |
39 | # Compiled binary addons (https://nodejs.org/api/addons.html)
40 | build/Release
41 |
42 | # Dependency directories
43 | node_modules/
44 | jspm_packages/
45 |
46 | # Snowpack dependency directory (https://snowpack.dev/)
47 | web_modules/
48 |
49 | # TypeScript cache
50 | *.tsbuildinfo
51 |
52 | # Optional npm cache directory
53 | .npm
54 |
55 | # Optional eslint cache
56 | .eslintcache
57 |
58 | # Optional stylelint cache
59 | .stylelintcache
60 |
61 | # Microbundle cache
62 | .rpt2_cache/
63 | .rts2_cache_cjs/
64 | .rts2_cache_es/
65 | .rts2_cache_umd/
66 |
67 | # Optional REPL history
68 | .node_repl_history
69 |
70 | # Output of 'npm pack'
71 | *.tgz
72 |
73 | # Yarn Integrity file
74 | .yarn-integrity
75 |
76 | # dotenv environment variable files
77 | .env
78 | .env.development.local
79 | .env.test.local
80 | .env.production.local
81 | .env.local
82 |
83 | # parcel-bundler cache (https://parceljs.org/)
84 | .cache
85 | .parcel-cache
86 |
87 | # Next.js build output
88 | .next
89 | out
90 |
91 | # Nuxt.js build / generate output
92 | .nuxt
93 | dist
94 |
95 | # Gatsby files
96 | .cache/
97 | # Comment in the public line in if your project uses Gatsby and not Next.js
98 | # https://nextjs.org/blog/next-9-1#public-directory-support
99 | # public
100 |
101 | # vuepress build output
102 | .vuepress/dist
103 |
104 | # vuepress v2.x temp and cache directory
105 | .temp
106 |
107 | # Docusaurus cache and generated files
108 | .docusaurus
109 |
110 | # Serverless directories
111 | .serverless/
112 |
113 | # FuseBox cache
114 | .fusebox/
115 |
116 | # DynamoDB Local files
117 | .dynamodb/
118 |
119 | # TernJS port file
120 | .tern-port
121 |
122 | # Stores VSCode versions used for testing VSCode extensions
123 | .vscode-test
124 |
125 | # yarn v2
126 | .yarn/cache
127 | .yarn/unplugged
128 | .yarn/build-state.yml
129 | .yarn/install-state.gz
130 | .pnp.*
131 |
132 | ### Node Patch ###
133 | # Serverless Webpack directories
134 | .webpack/
135 |
136 | # Optional stylelint cache
137 |
138 | # SvelteKit build / generate output
139 | .svelte-kit
140 |
141 | ### VisualStudioCode ###
142 | .vscode/*
143 | !.vscode/settings.json
144 | !.vscode/tasks.json
145 | !.vscode/launch.json
146 | !.vscode/extensions.json
147 | !.vscode/*.code-snippets
148 |
149 | # Local History for Visual Studio Code
150 | .history/
151 |
152 | # Built Visual Studio Code Extensions
153 | *.vsix
154 |
155 | ### VisualStudioCode Patch ###
156 | # Ignore all local history of files
157 | .history
158 | .ionide
159 |
160 | # Support for Project snippet scope
161 |
162 | # End of https://www.toptal.com/developers/gitignore/api/node,visualstudiocode
163 | # Support for Project snippet scope
164 |
165 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Add files here to ignore them from prettier formatting
2 |
3 | dist/
4 | bin/
5 | lib/
6 | node_modules/
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "useTabs": false,
4 | "tabWidth": 4,
5 | "printWidth": 90,
6 | "overrides": [
7 | {
8 | "files": ["*.yml", "*.yaml"],
9 | "options": {
10 | "tabWidth": 2
11 | }
12 | },
13 | {
14 | "files": ["*.scss"],
15 | "options": {
16 | "printWidth": 100
17 | }
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | // 📙 sub-libs
4 | // "tools": true,
5 | ".design": true,
6 |
7 | // ⚙️ config
8 | "**/**/*code-workspace": true,
9 | ".cocorc": true,
10 | // "**/**/package.json": true,
11 |
12 | // 🔄️ CI/CD
13 | ".github": true,
14 |
15 | // 🧼 linters & styles
16 | "**/**/.prettierrc": true,
17 | "**/**/*.eslintrc.*": true,
18 | ".prettierignore": true,
19 |
20 | // 📝 readmes
21 | // "**/**/README.md": true,
22 | "LICENSE": true,
23 |
24 | // 🗑️
25 | "node_modules": true,
26 | "package-lock.json": true,
27 | // ".gitignore": true,
28 | ".git": true,
29 | // "dist": true,
30 | ".vscode": true,
31 |
32 | // 🧪 tests
33 | "**/**/test": true,
34 | "**/**/tests": true,
35 | "**/**/*.specs.ts": true,
36 | "**/**/*.specs.js": true,
37 | },
38 | "scss.lint.emptyRules": "ignore"
39 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | ## [0.2.1] - 2024-10-19
6 |
7 | ### Fixes
8 | - fix: 🚑 some css customizations not showing after 1.22.3 gitea update
9 |
10 | ## [0.2.0] - 2024-10-19
11 |
12 | ### Added
13 | - Pinned the project to the Gitea 1.22.3 version.
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 lucas-labs
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | lucaslabs
› Gitea theme
3 |
4 |
5 | *Theme for `lucaslabs` internal gitea server.*
6 |
7 | > [!IMPORTANT]
8 | > From version `v1.0.0` onwards, `gitea>=1.23` is required.
9 | > In case you're looking for `gitea@1.22.x` support, [`v0.2.2`](https://github.com/lucas-labs/gitea-lugit-theme/releases/tag/v0.2.2)
10 | > is the last version that supports it.
11 |
12 | ## Preview
13 |
14 | 
15 |
16 |
17 | Issue Page
18 |
19 |
20 |
21 |
22 | Settings Page
23 |
24 |
25 |
26 |
27 | PR Page
28 |
29 |
30 |
31 | ## Usage
32 |
33 | 1. Go to the [releases page](https://github.com/lucas-labs/gitea-lugit-theme/releases) and get the latest `gitea-lugit-theme.tar.gz` release file.
34 | 2. Place the `templates` and `public` folder in your `$GITEA_CUSTOM` directory.
35 | 3. Append the themes in your `app.ini` file:
36 |
37 | ```ini
38 | [ui]
39 | THEMES=...,dark,light,auto
40 | DEFAULT_THEME=dark # optional
41 | ```
42 |
43 | > 💡 You can change the names of the themes by changing the name of the theme files in `public/css/theme-{name}.css` and in the `app.ini` file, accordingly.
44 |
45 | 4. Restart `gitea`.
46 |
47 | ## Credits
48 |
49 | - [`catppuccin/gitea`](https://github.com/catppuccin/gitea), these themes are based on them.
50 |
51 | ## Development
52 |
53 | ### build
54 | ```bash
55 | $ npm install
56 | $ npm run build
57 | ```
58 |
59 | ### serve
60 | ```bash
61 | $ npm run serve -- --server path/to/gitea/custom
62 |
63 | # e.g.
64 |
65 | # on linux
66 | $ npm run serve -- --server ~/gitea/custom
67 | # on windows
68 | $ npm run serve -- -- --server c:/gitea/custom
69 | ```
70 |
71 | ## Contributing
72 |
73 | Feel free to open an issue or a pull request. Contributions are welcome!
--------------------------------------------------------------------------------
/figs/issue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucas-labs/gitea-lugit-theme/981d64f1998fb0f25186560f4907b45979b29f64/figs/issue.png
--------------------------------------------------------------------------------
/figs/pr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucas-labs/gitea-lugit-theme/981d64f1998fb0f25186560f4907b45979b29f64/figs/pr.png
--------------------------------------------------------------------------------
/figs/repo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucas-labs/gitea-lugit-theme/981d64f1998fb0f25186560f4907b45979b29f64/figs/repo.png
--------------------------------------------------------------------------------
/figs/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lucas-labs/gitea-lugit-theme/981d64f1998fb0f25186560f4907b45979b29f64/figs/settings.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@lucas-labs/lugit-theme",
3 | "version": "1.0.1",
4 | "type": "module",
5 | "description": "Custom theme for lucaslabs' internal git server",
6 | "main": "index.js",
7 | "scripts": {
8 | "build": "node tools/build.js",
9 | "serve": "node tools/serve.js",
10 | "deploy": "node tools/deploy.js",
11 | "restart": "node tools/restart.js",
12 | "style:check": "npx prettier -c ."
13 | },
14 | "author": "lucas-labs",
15 | "license": "MIT",
16 | "devDependencies": {
17 | "browser-sync": "^2.29.3",
18 | "chokidar": "^3.5.3",
19 | "css-tree": "^2.3.1",
20 | "eslint": "^8.43.0",
21 | "fabric": "5.3",
22 | "imagemin-zopfli": "^7.0.0",
23 | "prettier": "^2.8.8",
24 | "sass": "1.63.6",
25 | "svgo": "^3.0.2"
26 | },
27 | "dependencies": {
28 | "@lucas-labs/lui-micro": "^3.1.4"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/themes/img/favicon.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/themes/img/loading.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/src/themes/img/logo.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/themes/scss/_utils/_color-utils.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:color';
2 | @use 'sass:map';
3 |
4 | @function light-change($color, $amount) {
5 | @return color.adjust($color, $lightness: $amount * -1);
6 | }
7 |
8 | @function alpha-change($color, $alpha) {
9 | @return color.change($color, $alpha: $alpha);
10 | }
11 |
12 | @function color-variants-light($color, $with-base: false) {
13 | $set: (
14 | '3%': light-change($color, -3%),
15 | '6%': light-change($color, -6%),
16 | '9%': light-change($color, -9%),
17 | '10%': light-change($color, -10%),
18 | '12%': light-change($color, -12%),
19 | '15%': light-change($color, -15%),
20 | '20%': light-change($color, -20%),
21 | '25%': light-change($color, -25%),
22 | '30%': light-change($color, -30%),
23 | '35%': light-change($color, -35%),
24 | '40%': light-change($color, -40%),
25 | '45%': light-change($color, -45%),
26 | '50%': light-change($color, -50%),
27 | '55%': light-change($color, -55%),
28 | '60%': light-change($color, -60%),
29 | '65%': light-change($color, -65%),
30 | '70%': light-change($color, -70%),
31 | '75%': light-change($color, -75%),
32 | '80%': light-change($color, -80%),
33 | '85%': light-change($color, -85%),
34 | '90%': light-change($color, -90%),
35 | '95%': light-change($color, -95%),
36 | '100%': light-change($color, -100%)
37 | );
38 |
39 | @if $with-base {
40 | $set: map.merge((base: $color), $set);
41 | }
42 |
43 | @return $set;
44 | }
45 |
46 | @function color-variants-dark($color, $with-base: false) {
47 | $set: (
48 | '3%': light-change($color, 3%),
49 | '6%': light-change($color, 6%),
50 | '9%': light-change($color, 9%),
51 | '10%': light-change($color, 10%),
52 | '12%': light-change($color, 12%),
53 | '15%': light-change($color, 15%),
54 | '20%': light-change($color, 20%),
55 | '25%': light-change($color, 25%),
56 | '30%': light-change($color, 30%),
57 | '35%': light-change($color, 35%),
58 | '40%': light-change($color, 40%),
59 | '45%': light-change($color, 45%),
60 | '50%': light-change($color, 50%),
61 | '55%': light-change($color, 55%),
62 | '60%': light-change($color, 60%),
63 | '65%': light-change($color, 65%),
64 | '70%': light-change($color, 70%),
65 | '75%': light-change($color, 75%),
66 | '80%': light-change($color, 80%),
67 | '85%': light-change($color, 85%),
68 | '90%': light-change($color, 90%),
69 | '95%': light-change($color, 95%),
70 | '100%': light-change($color, 100%)
71 | );
72 |
73 | @if $with-base {
74 | $set: map.merge((base: $color), $set);
75 | }
76 |
77 | @return $set;
78 | }
79 |
80 | @function variants($color, $light: true, $dark: true, $base: true, $override-base-with: null) {
81 | $set: ();
82 |
83 | @if $light {
84 | $set: map.merge($set, (light: color-variants-light($color)));
85 | }
86 |
87 | @if $dark {
88 | $set: map.merge($set, (dark: color-variants-dark($color)));
89 | }
90 |
91 | @if $base {
92 | @if $override-base-with {
93 | $set: map.merge($set, (base: $override-base-with));
94 | } @else {
95 | $set: map.merge($set, (base: $color));
96 | }
97 | }
98 |
99 | @return $set;
100 | }
--------------------------------------------------------------------------------
/src/themes/scss/_vars.scss:
--------------------------------------------------------------------------------
1 | $variables: (
2 | 'font-size': (
3 | 'xs': .714rem,
4 | 'sm': .857rem,
5 | 'md': 1rem, // 14
6 | 'lg': 1.143rem, // 16
7 | 'xl': 1.286rem, // 18
8 | '2xl': 1.429rem, // 20
9 | '3xl': 1.714rem, // 24
10 | '4xl': 2rem, // 28
11 | ),
12 | 'font-family': '-apple-system, "Segoe UI", system-ui, "SF Pro Text", Inter, Roboto, "Helvetica Neue", Arial, sans-serif',
13 | 'code-font-family': 'ui-monospace, SFMono-Regular, "Source Code Pro", "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace, var(--fonts-emoji)',
14 | 'emoji-font-family': '"Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla"',
15 | 'measure': (
16 | '.25x': 4px,
17 | '.375x': 6px,
18 | '.5x': 8px,
19 | '.75x': 12px,
20 | '.875x': 14px,
21 | '1x': 16px,
22 | '1.25x': 20px,
23 | '1.5x': 24px,
24 | '2x': 32px,
25 | '2.5x': 40px,
26 | '3x': 48px,
27 | '4x': 64px,
28 | ),
29 | 'navbar-logo': (
30 | 'width': 56px,
31 | 'height': 20px,
32 | ),
33 | 'repo-home': (
34 | 'sidebar-width': 296px,
35 | )
36 | );
--------------------------------------------------------------------------------
/src/themes/scss/auto.scss:
--------------------------------------------------------------------------------
1 | @import "./theme-light.css" (prefers-color-scheme: light);
2 | @import "./theme-dark.css" (prefers-color-scheme: dark);
--------------------------------------------------------------------------------
/src/themes/scss/dark.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro' as lui;
2 | @use './_utils/color-utils' as c;
3 | @use './theme' as theme;
4 | @use './vars' as vars;
5 |
6 | $is-dark: true;
7 | $brand: #a6c6f7;
8 |
9 | $colors: (
10 | primary: c.variants($brand),
11 | secondary: c.variants(hsl(240, 18%, 14%)), // same as elevation/6
12 | text: #e6edf3,
13 | subtle: hsl(240, 10%, 70%), // same as elevation/10
14 | palette: (
15 | 'red': c.variants(#f38ba8), // red
16 | 'orange': c.variants(#fab387), // peach
17 | 'yellow': c.variants(#f9e2af), // yellow
18 | 'olive': c.variants(#e2f095),
19 | 'green': c.variants(#00E676), // green
20 | 'teal': c.variants(#94e2d5), // teal
21 | 'blue': c.variants(#89b4fa), // blue
22 | 'violet': c.variants(#b4befe), // lavender
23 | 'purple': c.variants(#9b6bf5), // mauve
24 | 'pink': c.variants(#f5c2e7), // pink
25 | 'brown': c.variants(#f2cdcd), // flamingo
26 | 'black': c.variants(#181825), // black
27 | 'white': c.variants(#e6edf3), // white
28 | ),
29 | elevation: (
30 | '1': hsl(240, 33%, 01%),
31 | '2': hsl(240, 33%, 04%),
32 | '3': hsl(240, 33%, 06%),
33 | '4': hsl(240, 30%, 08%),
34 | '5': hsl(240, 28%, 12%),
35 | '6': hsl(240, 25%, 14%),
36 | '7': hsl(240, 20%, 30%),
37 | '8': hsl(240, 20%, 40%),
38 | '9': hsl(240, 10%, 50%),
39 | '10': hsl(240, 10%, 70%),
40 | '11': hsl(240, 10%, 80%),
41 | '12': hsl(240, 10%, 90%),
42 | )
43 | );
44 |
45 | // init lui-micro, css-vars only (no reboot, no basic)
46 | @include lui.init(
47 | $theme: (colors: $colors, variables: vars.$variables),
48 | $options: (
49 | reboot: false,
50 | basic: false,
51 | fg-var-name: 'text',
52 | bg-var-name: 'elevation/1',
53 | ),
54 | );
55 |
56 | @include theme.make-theme($is-dark);
--------------------------------------------------------------------------------
/src/themes/scss/light.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro' as lui;
2 | @use './_utils/color-utils' as c;
3 | @use './theme' as theme;
4 | @use './vars' as vars;
5 |
6 | $is-dark: false;
7 | $brand: #6296e2;
8 |
9 | $colors: (
10 | primary: c.variants($brand),
11 | secondary: c.variants(#bcc0cc),
12 | text: #484b60,
13 | subtle: #656c90, // same as elevation/10
14 | palette: (
15 | 'red': c.variants(#d20f39), // red
16 | 'orange': c.variants(#fe640b), // peach
17 | 'yellow': c.variants(#df8e1d), // yellow
18 | 'olive': c.variants(#e2f095),
19 | 'green': c.variants(#34ac56), // green
20 | 'teal': c.variants(#179299), // teal
21 | 'blue': c.variants(#1e66f5), // blue
22 | 'violet': c.variants(#7287fd), // lavender
23 | 'purple': c.variants(#8652e7), // mauve
24 | 'pink': c.variants(#ea76cb), // pink
25 | 'brown': c.variants(#dd7878), // flamingo
26 | 'black': c.variants(#181825), // black
27 | 'white': c.variants(#e6edf3), // white
28 | ),
29 | elevation: (
30 | '1': #fcfcfd, // elevation/1
31 | '2': #f6f7f9, // elevation/2
32 | '3': #eff1f5, // elevation/3
33 | '4': #e6e9ef, // elevation/4
34 | '5': #d7dce6, // elevation/5
35 | '6': #bcc0cc, // elevation/6
36 | '7': #9ba7bf, // elevation/7
37 | '8': #838fae, // elevation/8
38 | '9': #717a9f, // elevation/9
39 | '10': #656c90, // elevation/10
40 | '11': #565b77, // elevation/11
41 | '12': #484b60, // elevation/12
42 | )
43 | );
44 |
45 | // init lui-micro, css-vars only (no reboot, no basic)
46 | @include lui.init(
47 | $theme: (colors: $colors, variables: vars.$variables),
48 | $options: (
49 | reboot: false,
50 | basic: false,
51 | fg-var-name: 'text',
52 | bg-var-name: 'elevation/1',
53 | ),
54 | );
55 |
56 | @include theme.make-theme($is-dark);
--------------------------------------------------------------------------------
/src/themes/scss/theme/components/_avatar.scss:
--------------------------------------------------------------------------------
1 | @mixin avatar {
2 | img.ui.avatar, .ui.avatar img, .ui.avatar svg {
3 | &:not(.org-avatar) {
4 | border-radius: 50% !important;
5 | object-fit: fill;
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/src/themes/scss/theme/components/_boxes.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/var' as var;
2 | @use '@lucas-labs/lui-micro/color' as color;
3 |
4 | @mixin segment {
5 | .ui.segment {
6 | margin: var.get('measure/1x') 0;
7 | padding: var.get('measure/1x');
8 | border-radius: var(--border-radius);
9 | }
10 |
11 | .ui.segment form >*:first-child {
12 | margin-top: 0;
13 | }
14 |
15 | .ui.segment form >*:last-child {
16 | margin-bottom: 0;
17 | }
18 |
19 | .ui.top.attached.header {
20 | border-radius: var(--border-radius) var(--border-radius) 0 0;
21 | }
22 |
23 | .ui.attached.segment:has(+.ui[class*="top attached"].header), .ui.attached.segment:has(+.page.buttons), .ui.attached.segment:last-child, .ui.segment:has(+.ui.segment:not(.attached)), .ui.attached.segment:has(+.ui.modal) {
24 | border-bottom-left-radius: var(--border-radius);
25 | border-bottom-right-radius: var(--border-radius);
26 | }
27 |
28 | .ui.segments:not(.horizontal)>.segment:first-child, .ui.segments.horizontal>.segment:first-child {
29 | border-radius: var(--border-radius);
30 | }
31 |
32 | .ui.segments:not(.horizontal)>.segment:last-child, .ui.horizontal.segments>.segment:last-child {
33 | border-radius: var(--border-radius);
34 | }
35 | }
36 |
37 | @mixin comments {
38 | .comment {
39 | .content {
40 | background-color: var(--color-box-body);
41 |
42 | >.comment-header, >.ui.segment {
43 | &:before, &:after {
44 | display: none;
45 | }
46 | }
47 |
48 | .comment-header {
49 | padding: var.get('measure/.25x') var.get('measure/.25x') var.get('measure/.25x') var.get('measure/1x') !important;
50 |
51 | .comment-header-left {
52 | .text {
53 | color: color.get('subtle') !important;
54 |
55 | .author {
56 | color: color.get('text') !important;
57 | }
58 | }
59 | }
60 |
61 | .comment-header-right {
62 | * {
63 | color: color.get('subtle') !important;
64 | }
65 |
66 | .label {
67 | height: var.get('measure/1.25x', 1.25rem) !important;
68 | padding: 0px var.get('measure/.375x') !important;
69 | border-radius: var.get('measure/1x') !important;
70 | }
71 | }
72 | }
73 | }
74 | }
75 |
76 | .comment-list .comment>.content>div:last-child {
77 | border-bottom-left-radius: var(--border-radius) !important;
78 | border-bottom-right-radius: var(--border-radius) !important;
79 | }
80 |
81 | .comment-list .comment>.content>div:first-child {
82 | border-top-left-radius: var(--border-radius) !important;
83 | border-top-right-radius: var(--border-radius) !important;
84 | }
85 |
86 | .ui.comments .comment {
87 | margin: var.get('measure/.25x') 0 0;
88 | }
89 | }
--------------------------------------------------------------------------------
/src/themes/scss/theme/components/_editor.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/var' as var;
2 | @use '@lucas-labs/lui-micro/color' as color;
3 |
4 | @mixin editor {
5 | .combo-markdown-editor {
6 | .top.tabular.menu {
7 | background-color: var(--color-box-header);
8 | border-radius: var(--border-radius) var(--border-radius) 0 0;
9 | min-height: 0px;
10 | margin-bottom: var.get('measure/1x') !important;
11 |
12 | .item {
13 | margin: -1px 0 0 -1px; // merge borders
14 | border-top-left-radius: var(--border-radius) !important;
15 | border-top-right-radius: var(--border-radius) !important;
16 | padding: var.get('measure/.5x') var.get('measure/.75x') !important;
17 | font-weight: 400;
18 |
19 | &:hover {
20 | color: var(--color-text-light-2);
21 | }
22 |
23 | &.active {
24 | color: var(--color-text);
25 | &:after {
26 | // a hacky 1 pixel "button border" to make the border
27 | // of the whole menu disappear under the active tab
28 | display: block;
29 | content: '';
30 | position: absolute;
31 | top: 100%;
32 | right: 0;
33 | width: 100%;
34 | height: 1px;
35 | background-color: var(--color-body);
36 | }
37 | }
38 | }
39 | }
40 |
41 | .tab {
42 | .EasyMDEContainer { // legacy editor
43 | border: none !important;
44 |
45 | .editor-toolbar {
46 | border: none !important;
47 | }
48 | }
49 |
50 | markdown-toolbar, .EasyMDEContainer .editor-toolbar {
51 | padding: 0 10px !important;
52 |
53 | .markdown-toolbar-group {
54 | border: none !important;
55 | padding: 0 !important;
56 |
57 | &:not(:last-child) {
58 | &:after {
59 | content: '';
60 | display: block;
61 | position: relative;
62 | width: 1px;
63 | flex: 1;
64 | margin: var.get('measure/.375x') var.get('measure/.375x') !important;
65 | background-color: color.get('elevation/6');
66 | }
67 | }
68 | }
69 |
70 | .markdown-toolbar-button, button {
71 | line-height: 0;
72 | display: inline-block;
73 | color: var(--color-text-light-2) !important;
74 | padding: var.get('measure/.375x') !important;
75 | transition: background-color .1s ease;
76 | border-radius: var.get('measure/.375x') !important;
77 | height: auto;
78 | min-width: fit-content;
79 |
80 | &:hover {
81 | background-color: color.get('elevation/5') !important;
82 | }
83 |
84 | // if has attribute level
85 | &[level] {
86 | width: 34px;
87 | }
88 |
89 | // if aria-checked is true
90 | &[aria-checked="true"] {
91 | background-color: color.get('elevation/4');
92 | }
93 | }
94 |
95 | button {
96 | &:after {
97 | vertical-align: unset !important;
98 | }
99 | }
100 | }
101 |
102 | textarea, .CodeMirror.cm-s-easymde.CodeMirror-wrap {
103 | background-color: transparent !important;
104 | border: none !important;
105 | padding: var.get('measure/1x') var.get('measure/1x') !important;
106 | }
107 |
108 | .editor-statusbar {
109 | margin-bottom: 0 !important;
110 | }
111 |
112 | &.markup {
113 | padding: 0 var.get('measure/1x') var.get('measure/1x');
114 | }
115 | }
116 | }
117 |
118 | .combo-markdown-editor {
119 | border: 1px solid var(--color-secondary);
120 | border-radius: var(--border-radius);
121 | &:focus-within {
122 | outline: 2px solid var(--color-accent);
123 | border-radius: var(--border-radius);
124 | }
125 |
126 | textarea:focus {
127 | outline: none !important;
128 | }
129 |
130 | .ui.tab.markup[data-tab-panel=markdown-previewer] {
131 | border-bottom: 0px;
132 | }
133 | }
134 |
135 | #comment-form, .edit-content-zone, .comment-form, .comment-code-cloud form {
136 | padding: 0 !important;
137 | border: none !important;
138 |
139 | .field {
140 | margin-bottom: var.get('measure/1x') !important;
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/themes/scss/theme/components/_file-content.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/var' as var;
2 | @use '@lucas-labs/lui-micro/color' as color;
3 |
4 | @mixin file-content {
5 | .non-diff-file-content {
6 | .ui.segment.list-header {
7 | border-radius: var(--border-radius);
8 | margin-bottom: var.get('measure/1x') !important;
9 | gap: var.get('measure/.5x');
10 | padding: var.get('measure/.5x') var.get('measure/.75x');
11 |
12 | .latest-commit {
13 | gap: var.get('measure/.5x');
14 | }
15 |
16 | .commit-summary {
17 | color: color.get('subtle');
18 | }
19 |
20 | .age {
21 | font-size: 12px;
22 | }
23 | }
24 |
25 | .ui.top.attached.header {
26 | border-top-left-radius: var(--border-radius);
27 | border-top-right-radius: var(--border-radius);
28 |
29 | .file-info {
30 | color: color.get('subtle') !important;
31 | }
32 |
33 | .btn-octicon {
34 | --color-text: #{color.get('subtle')};
35 | }
36 | }
37 |
38 | .ui.attached.segment:has(+.ui[class*="top attached"].header), .ui.attached.segment:has(+.page.buttons), .ui.attached.segment:last-child, .ui.segment:has(+.ui.segment:not(.attached)), .ui.attached.segment:has(+.ui.modal) {
39 | border-top-left-radius: 0;
40 | border-top-right-radius: 0;
41 | border-bottom-left-radius: var(--border-radius);
42 | border-bottom-right-radius: var(--border-radius);
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/src/themes/scss/theme/components/_forms.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/var' as var;
2 | @use '@lucas-labs/lui-micro/color' as color;
3 | @use './forms/' as forms;
4 |
5 | @mixin forms {
6 | @include forms.dropdown-menu;
7 | @include forms.menu;
8 | @include forms.tabular-menu;
9 | @include forms.input;
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/src/themes/scss/theme/components/_labels.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/color' as color;
2 | @use '@lucas-labs/lui-micro/var' as var;
3 |
4 | @mixin label-signed {
5 | .label.isSigned {
6 | font-size: var.get('font-size/sm') !important;
7 | margin: 0px .25em !important;
8 | padding: 0 !important;
9 | display: inline-flex !important;
10 | gap: 0px !important;
11 | border-radius: 16px !important;
12 |
13 | --color-light-border: rgba(#{color.get('palette/green/base', 'rgb')}, 1) !important;
14 | --color-label-bg: none !important;
15 | --color-text: rgba(#{color.get('palette/green/base', 'rgb')}, 1) !important;
16 | --color-green-badge-bg: none !important;
17 | --color-green-badge-hover-bg: rgba(#{color.get('palette/green/base', 'rgb')}, 0.05) !important;
18 | --color-label-hover-bg: rgba(#{color.get('palette/green/base', 'rgb')}, 0.05) !important;
19 | --color-label-text: rgba(#{color.get('palette/green/base', 'rgb')}, 1) !important;
20 | --color-green-badge: rgba(#{color.get('palette/green/base', 'rgb')}, 1) !important;
21 |
22 | .shortsha {
23 | padding: 2px 6px 2px 8px !important;
24 | }
25 |
26 | .ui.detail.icon.button {
27 | opacity: 1 !important;
28 | padding: 2px 8px 2px 6px !important;
29 | margin: 0 !important;
30 | background: none !important;
31 | border-color: rgba(#{color.get('palette/green/base', 'rgb')}, 1) !important;
32 | }
33 |
34 | &.isVerified {
35 | .ui.detail.icon.button {
36 | padding: 2px 8px 2px 6px !important;
37 | }
38 | }
39 | }
40 | }
41 |
42 | @mixin label-default {
43 | .ui.label {
44 | border-radius: var.get('measure/1.25x');
45 |
46 | &.scope-left {
47 | border-top-right-radius: 0;
48 | border-bottom-right-radius: 0;
49 | }
50 |
51 | &.scope-right {
52 | border-top-left-radius: 0;
53 | border-bottom-left-radius: 0;
54 | }
55 |
56 | &.green {
57 | color: var(--color-green-contrast) !important;
58 | }
59 |
60 | &.red {
61 | color: var(--color-red-contrast) !important;
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/src/themes/scss/theme/components/_markup.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/color' as color;
2 | @use '@lucas-labs/lui-micro/var' as var;
3 |
4 | @mixin markup {
5 | .markup {
6 | .task-list-item input[type=checkbox] {
7 | margin: 0 .5em .25em -1.4em;
8 | }
9 |
10 | input[type=checkbox] {
11 | --border-radius: #{var.get('measure/.25x')};
12 | width: var.get('measure/1x');
13 | height: var.get('measure/1x');
14 | margin-right: 4px;
15 | --color-input-background: #{color.get('elevation/5')};
16 |
17 | &:checked {
18 | background-color: var(--color-primary);
19 | }
20 |
21 | &:after {
22 | --color-text: var(--color-primary-contrast);
23 | }
24 |
25 | &:not([disabled]):hover, &:not([disabled]):active {
26 | border-color: var(--color-secondary);
27 | background-color: color.get('elevation/6');
28 |
29 | &:checked {
30 | background-color: var(--color-primary-hover);
31 | }
32 | }
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/src/themes/scss/theme/components/button/index.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/var' as var;
2 | @use '@lucas-labs/lui-micro/color' as color;
3 |
4 | @mixin hollow {
5 | --border-radius: #{var.get('measure/.25x')};
6 |
7 | padding: var.get('measure/.375x') var.get('measure/.5x') !important;
8 | background-color: transparent !important;
9 | font-weight: normal;
10 |
11 | button, .button {
12 | border-color: color.get('elevation/6') !important;
13 | gap: var.get('measure/.5x') !important;
14 | border-radius: var(--border-radius);
15 |
16 | &:hover {
17 | border-color: color.get('elevation/7') !important;
18 |
19 | +.label {
20 | border-left-color: color.get('elevation/7') !important;
21 | }
22 | }
23 | }
24 |
25 | &.labeled {
26 | button, .button {
27 | border-top-right-radius: 0;
28 | border-bottom-right-radius: 0;
29 | }
30 |
31 | .label {
32 | border-color: color.get('elevation/6') !important;
33 | background-color: transparent !important;
34 |
35 | border-top-left-radius: 0;
36 | border-bottom-left-radius: 0;
37 | border-top-right-radius: var(--border-radius);
38 | border-bottom-right-radius: var(--border-radius);
39 |
40 |
41 | &:hover {
42 | border-color: color.get('elevation/7') !important;
43 | }
44 | }
45 | }
46 |
47 | &:hover {
48 | border-color: color.get('elevation/7') !important;
49 | }
50 | }
51 |
52 | @mixin gitea-button {
53 | .ui.icon.buttons .button, .ui.icon.button:not(.compact) {
54 | // padding: 50px;
55 | }
56 |
57 | .code-comment-buttons-buttons {
58 | button:not(.labeled):not(.icon) {
59 | padding: .78571429em !important;
60 | }
61 | }
62 |
63 | // buttons group
64 | .ui.buttons {
65 | &.icon .button, &.icon:not(.compact) {
66 | padding: 50px 50px;
67 | }
68 |
69 | .button {
70 | &:first-child {
71 | border-top-left-radius: var(--border-radius);
72 | border-bottom-left-radius: var(--border-radius);
73 | border-top-right-radius: 0;
74 | border-bottom-right-radius: 0;
75 | }
76 |
77 | &.button:last-child {
78 | border-top-right-radius: var(--border-radius);
79 | border-bottom-right-radius: var(--border-radius);
80 | border-top-left-radius: 0;
81 | border-bottom-left-radius: 0;
82 | }
83 |
84 | border-right: none;
85 | flex: 1 0 auto;
86 | border-radius: 0;
87 | margin: 0;
88 | }
89 | }
90 |
91 | .ui.button {
92 | border-radius: var(--border-radius);
93 | transition: color .1s ease, background-color .1s ease, border-color .1s ease;
94 | padding: calc(#{var.get('measure/.375x')} - 1px) var.get('measure/1x');
95 | font-size: var.get('font-size/md');
96 | line-height: 20px;
97 |
98 | &.tiny {
99 | font-size: var.get('font-size/sm');
100 | line-height: var.get('font-size/sm');
101 | }
102 |
103 | &.basic {
104 | border-radius: var(--border-radius);
105 | }
106 |
107 | &.red, &.red.basic {
108 | border-color: var(--color-secondary);
109 | background: var(--color-button);
110 | color: var(--color-red);
111 |
112 | &:hover {
113 | background: var(--color-red-dark-2);
114 | color: var(--color-white);
115 | }
116 |
117 | &.basic {
118 | border-color: rgba(#{color.get('palette/red/base', 'rgb')}, 0.4);
119 | }
120 | }
121 |
122 | &.primary, &.red, &.basic {
123 | font-weight: 500;
124 | }
125 |
126 | &.green {
127 | color: var(--color-white);
128 | background: var(--color-green-dark-2);
129 | }
130 | }
131 | }
--------------------------------------------------------------------------------
/src/themes/scss/theme/components/forms/_input.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/color' as color;
2 | @use '@lucas-labs/lui-micro/var' as var;
3 |
4 | @mixin -all-inputs($state: null) {
5 | input#{$state},
6 | textarea#{$state},
7 | .ui.input>input#{$state},
8 | .ui.form input:not([type])#{$state},
9 | .ui.form select#{$state},
10 | .ui.form textarea#{$state},
11 | .ui.form input[type=date]#{$state},
12 | .ui.form input[type=datetime-local]#{$state},
13 | .ui.form input[type=email]#{$state},
14 | .ui.form input[type=file]#{$state},
15 | .ui.form input[type=number]#{$state},
16 | .ui.form input[type=password]#{$state},
17 | .ui.form input[type=search]#{$state},
18 | .ui.form input[type=tel]#{$state},
19 | .ui.form input[type=text]#{$state},
20 | .ui.form input[type=time]#{$state},
21 | .ui.form input[type=url]#{$state},
22 | .ui.selection.dropdown#{$state} {
23 | @content;
24 | }
25 | }
26 |
27 | @mixin input {
28 | @include -all-inputs {
29 | border-color: var(--color-input-border);
30 | color: var(--color-input-text);
31 | border-radius: var(--border-radius);
32 | line-height: 20px;
33 | padding: calc(#{var.get('measure/.375x')} - 1px) var.get('measure/.75x');
34 |
35 | +.ui.button:last-child {
36 | border-top-right-radius: var(--border-radius);
37 | border-bottom-right-radius: var(--border-radius);
38 | }
39 | }
40 |
41 | @include -all-inputs(':hover') {
42 | border-color: var(--color-input-border);
43 | color: var(--color-input-text)
44 | }
45 |
46 | @include -all-inputs(':focus') {
47 | border-radius: var(--border-radius);
48 | border-color: var(--color-input-border);
49 | color: var(--color-input-text);
50 | outline: 2px solid var(--color-accent);
51 | background-color: color.get('elevation/2');
52 | }
53 |
54 | @include -all-inputs('.active') {
55 | border-radius: var(--border-radius);
56 | outline: 2px solid var(--color-accent);
57 |
58 | > input.search {
59 | outline: none;
60 | }
61 | }
62 |
63 | .ui.action.input:not([class*="left action"])>input:focus {
64 | border-right-color: var(--color-input-border);
65 | z-index: 1;
66 | }
67 |
68 | .ui.action.input>.dropdown:not(:first-child):not(:last-child),
69 | .ui.action.input>.button:not(:first-child):not(:last-child),
70 | .ui.action.input>.buttons:not(:first-child):not(:last-child)>.button {
71 | border-radius: 0 !important;
72 | }
73 |
74 | .ui.action.input:not([class*="left action"])>input:focus+.ui.dropdown.selection,
75 | .ui.action.input:not([class*="left action"])>input:focus+.ui.dropdown.selection:hover,
76 | .ui.action.input:not([class*="left action"])>input:focus+.button,
77 | .ui.action.input:not([class*="left action"])>input:focus+.button:hover,
78 | .ui.action.input:not([class*="left action"])>input:focus+i.icon+.button,
79 | .ui.action.input:not([class*="left action"])>input:focus+i.icon+.button:hover {
80 | border-left-color: var(--color-input-border);
81 | }
82 |
83 | .ui.form .field>label {
84 | margin: 0 0 var.get('measure/.375x');
85 | font-size: var.get('font-size/md');
86 | font-weight: 600;
87 | }
88 |
89 | .ui.form textarea,
90 | .ui.form input:not([type]),
91 | .ui.form input[type=date],
92 | .ui.form input[type=datetime-local],
93 | .ui.form input[type=email],
94 | .ui.form input[type=number],
95 | .ui.form input[type=password],
96 | .ui.form input[type=search],
97 | .ui.form input[type=tel],
98 | .ui.form input[type=time],
99 | .ui.form input[type=text],
100 | .ui.form input[type=file],
101 | .ui.form input[type=url] {
102 | min-height: 2.71428571em;
103 | }
104 |
105 | .ui.selection.dropdown {
106 | min-height: 2.71428571em;
107 | >input {
108 | min-height: 0;
109 | &:focus {
110 | outline: none;
111 | }
112 | }
113 | }
114 |
115 | .ui.multiple.dropdown>.label, .ui.multiple.search.dropdown>input.search {
116 | padding: var.get('measure/.375x') var.get('measure/.75x');
117 | margin: .14285714rem .28571429rem .14285714rem 0;
118 | }
119 |
120 | .ui.multiple.search.dropdown>input.search {
121 | padding-left: 0;
122 | }
123 |
124 | .ui.checkbox label, .ui.radio.checkbox label {
125 | margin-left: 20px !important;
126 | }
127 | }
--------------------------------------------------------------------------------
/src/themes/scss/theme/components/forms/_menu.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/color' as color;
2 | @use '@lucas-labs/lui-micro/var' as var;
3 |
4 | @mixin dropdown-menu {
5 | .ui.dropdown {
6 | // dropdown menu
7 | >.menu {
8 | --border-radius: #{var.get('measure/.75x')};
9 |
10 | opacity: 0;
11 | overflow-x: hidden;
12 | &.hidden { opacity: 0 !important;}
13 | &.visible, &.show { opacity: 1 !important; }
14 |
15 | border-radius: var(--border-radius) !important;
16 | transition: opacity .2s ease !important;
17 | box-shadow: 0px 6px 12px -3px rgba(var(--color-shadow-rgb), 0.5),
18 | 0px 6px 18px 0px rgba(var(--color-shadow-rgb), 0.1) !important;
19 |
20 | .divider {
21 | margin-top: var.get('measure/.5x') !important;
22 | margin-bottom: var.get('measure/.5x') !important;
23 | }
24 |
25 | .item:not(.comment-reaction-button) {
26 | --border-radius: #{var.get('measure/.375x')};
27 |
28 | margin: 0 var.get('measure/.5x') !important;
29 | border-radius: var(--border-radius) !important;
30 | padding: var.get('measure/.375x') var.get('measure/.5x') !important;
31 | line-height: 1 !important;
32 | display: flex;
33 | flex-wrap: wrap;
34 | align-content: center;
35 | width: auto;
36 |
37 | &:first-of-type {
38 | margin-top: var.get('measure/.5x') !important;
39 | }
40 |
41 | &:last-of-type {
42 | margin-bottom: var.get('measure/.5x') !important;
43 | }
44 |
45 | &.clear-selection {
46 | margin-bottom: var.get('measure/.5x') !important;
47 | }
48 | }
49 |
50 | input[type="text"], input[name="search"] {
51 | --color-input-background: #{color.get('elevation/1')};
52 | --color-input-border: #{color.get('elevation/5')};
53 |
54 | min-height: var.get('measure/2x') !important;
55 | padding: 0 var.get('measure/.5x') !important;
56 | font-size: var.get('font-size/md') !important;
57 | border-radius: var.get('measure/.375x') !important;
58 |
59 | &:focus {
60 | --color-primary: var(--color-accent);
61 | }
62 | }
63 |
64 | .scrolling.menu {
65 | .item {
66 | gap: var.get('measure/.5x') !important;
67 |
68 | &:hover {
69 | &:before {
70 | position: absolute;
71 | top: calc(50% - 12px);
72 | left: -8px;
73 | width: 4px;
74 | height: 24px;
75 | content: "";
76 | background-color: var(--color-accent);
77 | border-radius: 6px;
78 | }
79 | }
80 |
81 | .item-check-mark {
82 | background-color: color.get('elevation/6');
83 | visibility: visible !important;
84 | display: inline-flex;
85 | padding: 2px;
86 | border-radius: var.get('measure/.375x');
87 | border: 1px solid color.get('elevation/8');
88 |
89 | svg {
90 | visibility: hidden;
91 | }
92 | }
93 |
94 | .item-secondary-info {
95 | flex-basis: 100%;
96 | padding: 0 0 0 var.get('measure/.5x');
97 | color: color.get('subtle');
98 | line-height: 100%;
99 | white-space: nowrap;
100 | overflow-x: hidden;
101 |
102 | small {
103 | white-space: nowrap;
104 | overflow: hidden;
105 | text-overflow: ellipsis;
106 | }
107 | }
108 |
109 | &.checked {
110 | .item-check-mark {
111 | background-color: var(--color-accent);
112 |
113 | svg {
114 | visibility: visible;
115 | }
116 | }
117 | }
118 | }
119 | }
120 | }
121 |
122 | // if not .upward
123 | &:not(.upward) {
124 | >.menu {
125 | top: calc(100% + var.get('measure/.5x')) !important;
126 | }
127 | }
128 |
129 | &.upward {
130 | >.menu {
131 | bottom: calc(100% + var.get('measure/.5x')) !important;
132 | }
133 | }
134 |
135 | &.upward.selection.visible, &.active.upward.selection {
136 | border-top-left-radius: var(--border-radius) !important;
137 | border-top-right-radius: var(--border-radius) !important;
138 | border-bottom-left-radius: var(--border-radius) !important;
139 | border-bottom-right-radius: var(--border-radius) !important;
140 | }
141 |
142 | &.selection.active, &.selection.active:hover, &.selection.active .menu, &.selection.active:hover .menu {
143 | border-color: var(--color-secondary) !important;
144 | }
145 |
146 | // select type dropdown
147 | &.selection {
148 | --border-radius: #{var.get('measure/.5x')};
149 | border-radius: var(--border-radius) !important;
150 |
151 | &:focus, &.active {
152 | border-color: var(--color-secondary) !important;
153 | outline: 2px solid var(--color-accent);
154 | }
155 |
156 | &.active {
157 | border-radius: var(--border-radius) !important;
158 | }
159 |
160 | >.menu {
161 | border-radius: var(--border-radius) !important;
162 | border-top-width: 1px !important;
163 | border-color: var(--color-secondary) !important;
164 | flex-direction: column;
165 | gap: var.get('measure/.25x');
166 |
167 | &.visible {
168 | display: flex;
169 | }
170 |
171 | .item {
172 | border: none !important;
173 | }
174 | }
175 | }
176 | }
177 | }
178 |
179 | @mixin menu {
180 | .ui.menu {
181 | border-radius: var(--border-radius);
182 |
183 | >.item:first-child {
184 | border-radius: var(--border-radius) 0 0 var(--border-radius);
185 | }
186 |
187 | &.compact:not(.secondary) .item:last-child {
188 | border-radius: 0 var(--border-radius) var(--border-radius) 0;
189 | }
190 | }
191 |
192 | .ui.secondary.menu {
193 | gap: var.get('measure/.25x');
194 |
195 | &.vertical {
196 | >.item {
197 | border: none;
198 | margin: 0 0 var.get('measure/.25x') 0;
199 | border-radius: var(--border-radius) !important;
200 | }
201 | }
202 |
203 | .dropdown.item.active:hover, a.item.active:hover {
204 | color: var(--color-text);
205 | background: var(--color-hover);
206 | }
207 | }
208 | }
209 |
210 | @mixin tabular-menu {
211 | .ui.tabular.menu .active.item, .ui.tabular.menu .active.item:hover {
212 | border-radius: var(--border-radius) var(--border-radius) 0 0 !important;
213 | }
214 | }
--------------------------------------------------------------------------------
/src/themes/scss/theme/components/forms/index.scss:
--------------------------------------------------------------------------------
1 | @forward './menu';
2 | @forward './input';
--------------------------------------------------------------------------------
/src/themes/scss/theme/components/index.scss:
--------------------------------------------------------------------------------
1 | @forward './labels';
2 | @forward './editor';
3 | @forward './forms';
4 | @forward './file-content';
5 | @forward './avatar';
6 | @forward './button';
7 | @forward './boxes';
8 | @forward './markup';
--------------------------------------------------------------------------------
/src/themes/scss/theme/index.scss:
--------------------------------------------------------------------------------
1 | @use './vars';
2 | @use './components';
3 | @use './modules';
4 |
5 | @mixin make-theme($is-dark: true) {
6 | @include vars.base;
7 | @include vars.colors($is-dark);
8 |
9 | // components
10 | @include components.label-default; // label for GPG signed commits
11 | @include components.label-signed; // label for GPG signed commits
12 | @include components.editor; // github-style (kinda) markdown editor in comments, issues, etc.
13 | @include components.forms; // github-style dropdown menus
14 | @include components.file-content; // github-style file content
15 | @include components.avatar; // github-style avatars
16 | @include components.gitea-button; // github-style buttons
17 | @include components.segment;
18 | @include components.comments; // github-style comment boxes
19 | @include components.markup; // styling rendered for markdown content
20 |
21 | // code highlighting
22 | @include modules.chroma($is-dark); // syntax highlighting
23 | @include modules.codemirror; // code editor
24 | @include modules.monaco; // vscode-like code editor
25 | @include modules.settings; // settings & profile pages
26 |
27 | // customized pages
28 | @include modules.repo; // repo page
29 | @include modules.issues; // issues page
30 | }
31 |
--------------------------------------------------------------------------------
/src/themes/scss/theme/modules/_chroma.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/color' as color;
2 | @use '@lucas-labs/lui-micro/var' as var;
3 |
4 | @mixin chroma($dark: false) {
5 | $orange: if($dark, 'palette/orange/base', 'palette/orange/dark/15%');
6 | $blue: if($dark, 'palette/blue/base', 'palette/blue/dark/15%');
7 | $fg: 'text';
8 | $subtle: 'subtle';
9 | $more-subtle: if($dark, 'elevation/8', 'elevation/8');
10 | $brown: if($dark, 'palette/brown/base', 'palette/brown/dark/15%');
11 | $red: if($dark, 'palette/red/base', 'palette/red/dark/15%');
12 | $green: if($dark, 'palette/green/base', 'palette/green/dark/10%');
13 | $teal: if($dark, 'palette/teal/base', 'palette/teal/dark/15%');
14 | $purple: if($dark, 'palette/purple/base', 'palette/purple/dark/10%');
15 | $yellow: if($dark, 'palette/yellow/base', 'palette/yellow/dark/25%');
16 | $violet: if($dark, 'palette/violet/base', 'palette/violet/dark/50%');
17 | $pink: if($dark, 'palette/pink/base', 'palette/pink/dark/25%');
18 | $blwh: if($dark, 'palette/white/base', 'palette/black/base');
19 |
20 | .chroma {
21 | .code-inner {
22 | font: 14px var.get('code-font-family');
23 | }
24 |
25 | .bp { color: color.get($orange); font-weight: 600;} // NameBuiltinPseudo
26 | .c { color: color.get($subtle); font-style: italic; } // Comment
27 | .c1 { color: color.get($subtle); font-style: italic; } // CommentSingle
28 | .ch { color: color.get($subtle); font-style: italic; } // CommentHashbang
29 | .cm { color: color.get($subtle); font-style: italic; } // CommentMultiline
30 | .cp { color: color.get($blue); } // CommentPreproc
31 | .cpf { color: color.get($blue); } // CommentPreprocFile
32 | .cs { color: color.get($subtle); } // CommentSpecial
33 | .dl { color: color.get($blue); } // LiteralStringDelimiter
34 | .fm {} // NameFunctionMagic
35 | .g {} // Generic
36 | .gd { color: color.get($blwh); background-color: rgba(#{color.get($red, 'rgb')}, .25); } // GenericDeleted
37 | .ge { font-style: italic; } // GenericEmph
38 | .gh { color: color.get($teal); } // GenericHeading
39 | .gi { color: color.get($blwh); background-color: rgba(#{color.get($green, 'rgb')}, 0.3); } // GenericInserted
40 | .gl {} // GenericUnderline
41 | .go { color: color.get($orange); } // GenericOutput
42 | .gp { color: color.get($subtle); font-weight: bold; } // GenericPrompt
43 | .gr { color: color.get($brown); } // GenericError
44 | .gs { font-weight: bold; } // GenericStrong
45 | .gt { color: color.get($brown); } // GenericTraceback
46 | .gu { color: color.get($teal); } // GenericSubheading
47 | .il { color: color.get($orange); } // LiteralNumberIntegerLong
48 | .k { color: color.get($purple); font-weight: 600; } // Keyword
49 | .kc { color: color.get($yellow); } // KeywordConstant
50 | .kd { color: color.get($purple); font-weight: 600; } // KeywordDeclaration
51 | .kn { color: color.get($yellow); } // KeywordNamespace
52 | .kp { color: color.get($purple); font-weight: 600; } // KeywordPseudo
53 | .kr { color: color.get($purple); font-weight: 600;} // KeywordReserved
54 | .kt { color: color.get($yellow); } // KeywordType
55 | .l {} // Literal
56 | .ld {} // LiteralDate
57 | .m { color: color.get($orange); } // LiteralNumber
58 | .mb { color: color.get($orange); } // LiteralNumberBin
59 | .mf { color: color.get($orange); } // LiteralNumberFloat
60 | .mh { color: color.get($orange); } // LiteralNumberHex
61 | .mi { color: color.get($orange); } // LiteralNumberInteger
62 | .mo { color: color.get($orange); } // LiteralNumberOct
63 | .n { color: color.get($violet); } // Name
64 | .na { color: color.get($yellow); } // NameAttribute
65 | .nb { color: color.get($orange); font-weight: 600; } // NameBuiltin
66 | .nc { color: color.get($pink); } // NameClass
67 | .nd { color: color.get($pink); } // NameDecorator
68 | .ne { color: color.get($brown); } // NameException
69 | .nf { color: color.get($blue); } // NameFunction
70 | .ni { color: color.get($pink); } // NameEntity
71 | .nl { color: color.get($yellow); } // NameLabel
72 | .nn { color: color.get($yellow); } // NameNamespace
73 | .no { color: color.get($yellow); } // NameConstant
74 | .nt { color: color.get($purple); } // NameTag
75 | .nv { color: color.get($orange); } // NameVariable
76 | .nx { color: color.get($orange); } // NameOther
77 | .o { color: color.get($teal); } // Operator
78 | .ow { color: color.get($teal); font-weight: bold; } // OperatorWord
79 | .p { color: color.get($subtle); } // Punctuation
80 | .py {} // NameProperty
81 | .s { color: color.get($green); } // LiteralString
82 | .s1 { color: color.get($green); } // LiteralStringSingle
83 | .s2 { color: color.get($green); } // LiteralStringDouble
84 | .sa { color: color.get($green); } // LiteralStringAffix
85 | .sb { color: color.get($green); } // LiteralStringBacktick
86 | .sc { color: color.get($green); } // LiteralStringChar
87 | .sd { color: color.get($green); } // LiteralStringDoc
88 | .se { color: color.get($blue); } // LiteralStringEscape
89 | .sh { color: color.get($green); } // LiteralStringHeredoc
90 | .si { color: color.get($green); } // LiteralStringInterpol
91 | .sr { color: color.get($blue); } // LiteralStringRegex
92 | .ss { color: color.get($green); } // LiteralStringSymbol
93 | .sx { color: color.get($green); } // LiteralStringOther
94 | .vc { color: color.get($yellow); } // NameVariableClass
95 | .vg { color: color.get($orange); } // NameVariableGlobal
96 | .vi { color: color.get($yellow); } // NameVariableInstance
97 | .vm {} // NameVariableMagic
98 | .w { color: color.get($more-subtle); } // TextWhitespace
99 | }
100 | }
--------------------------------------------------------------------------------
/src/themes/scss/theme/modules/_codemirror.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/color' as color;
2 |
3 | @mixin codemirror {
4 | .CodeMirror,
5 | .CodeMirror.cm-s-default,
6 | .CodeMirror.cm-s-paper {
7 | .cm-property {
8 | color: color.get('text')
9 | }
10 |
11 | .cm-header {
12 | color: color.get('text')
13 | }
14 |
15 | .cm-quote {
16 | color: color.get('palette/green/base')
17 | }
18 |
19 | .cm-keyword {
20 | color: color.get('palette/purple/base');
21 | }
22 |
23 | .cm-atom {
24 | color: color.get('palette/red/base');
25 | }
26 |
27 | .cm-number {
28 | color: color.get('palette/orange/base');
29 | }
30 |
31 | .cm-def {
32 | color: color.get('text')
33 | }
34 |
35 | .cm-variable-2 {
36 | color: color.get('palette/blue/base');
37 | }
38 |
39 | .cm-variable-3 {
40 | color: color.get('palette/teal/base');
41 | }
42 |
43 | .cm-comment {
44 | color: color.get('elevation/6')
45 | }
46 |
47 | .cm-string {
48 | color: color.get('palette/green/base')
49 | }
50 |
51 | .cm-string-2 {
52 | color: color.get('palette/green/base')
53 | }
54 |
55 | .cm-meta {
56 | color: color.get('palette/orange/base');
57 | }
58 |
59 | .cm-qualifier {
60 | color: color.get('palette/orange/base');
61 | }
62 |
63 | .cm-builtin {
64 | color: color.get('palette/orange/base');
65 | }
66 |
67 | .cm-bracket {
68 | color: color.get('text')
69 | }
70 |
71 | .cm-tag {
72 | color: color.get('palette/yellow/base');
73 | }
74 |
75 | .cm-attribute {
76 | color: color.get('palette/yellow/base');
77 | }
78 |
79 | .cm-hr {
80 | color: color.get('elevation/9');
81 | }
82 |
83 | .cm-url {
84 | color: color.get('palette/blue/base');
85 | }
86 |
87 | .cm-link {
88 | color: color.get('palette/blue/base');
89 | }
90 |
91 | .cm-error {
92 | color: color.get('palette/red/base');
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/themes/scss/theme/modules/_monaco.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/color' as color;
2 |
3 | @mixin monaco {
4 | $surface0: #{color.get('elevation/4')};
5 | $subtext0: #{color.get('elevation/10')};
6 | $overlay2: #{color.get('elevation/9')};
7 | $mantle: #{color.get('elevation/0')};
8 | $base: #{color.get('elevation/3')};
9 | $accent: #{color.get('primary/base')};
10 | $text: #{color.get('text')};
11 | $mauve: #{color.get('palette/purple/base')};
12 | $peach: #{color.get('palette/orange/base')};
13 | $teal: #{color.get('palette/teal/base')};
14 | $green: #{color.get('palette/green/base')};
15 | $blue: #{color.get('palette/blue/base')};
16 | $pink: #{color.get('palette/pink/base')};
17 |
18 | .monaco-editor {
19 | --vscode-editor-background: #{$mantle} !important;
20 | --vscode-editorGutter-background: #{$mantle} !important;
21 |
22 | // selected text
23 | .selected-text {
24 | background-color: $surface0 !important;
25 | }
26 | // line numbers
27 | .margin-view-overlays .line-numbers {
28 | color: $subtext0 !important;
29 | }
30 | .line-numbers.active-line-number {
31 | color: $accent !important;
32 | }
33 |
34 | // current / cursor line
35 | .view-overlays .current-line,
36 | .margin-view-overlays .current-line-margin {
37 | background-color: #{color.get('elevation/2')} !important;
38 | }
39 |
40 | // Note: all of the hotpink stuff is there so it's easily visible, since these editor scope mappings are a mess
41 |
42 | // plaintext
43 | .mtk1 {
44 | color: $text !important;
45 | }
46 | .mtk2 {
47 | color: #ff69b4 !important;
48 | }
49 | // decorators
50 | .mtk3 {
51 | color: $peach !important;
52 | }
53 | // shell arguments
54 | .mtk4 {
55 | color: $teal !important;
56 | }
57 | // css constants & pre-defineds
58 | .mtk5 {
59 | color: $text !important;
60 | }
61 | // keywords
62 | .mtk6 {
63 | color: $mauve !important;
64 | }
65 | // numbers
66 | .mtk7 {
67 | color: $peach !important;
68 | }
69 | // comments
70 | .mtk8 {
71 | color: $overlay2 !important;
72 | }
73 | // sometimes a keyword, apparently
74 | .mtk9 {
75 | color: $mauve !important;
76 | }
77 | // braces, brackets, parentheses
78 | .mtk10 {
79 | color: $subtext0 !important;
80 | }
81 | // arrow brackets & equal signs in HTML
82 | .mtk11 {
83 | color: $teal !important;
84 | }
85 | // @ sign in javascript ¯\_(ツ)_/¯
86 | .mtk12 {
87 | color: $teal !important;
88 | }
89 | .mtk13 {
90 | color: #ff69b4 !important;
91 | }
92 | .mtk14 {
93 | color: #ff69b4 !important;
94 | }
95 | // regex, css classnames, and HTML keywords (huh)
96 | .mtk15 {
97 | color: $mauve !important;
98 | }
99 | // shebangs
100 | .mtk16 {
101 | color: $overlay2 !important;
102 | }
103 | .mtk17 {
104 | color: #ff69b4 !important;
105 | }
106 | .mtk18 {
107 | color: #ff69b4 !important;
108 | }
109 | // glob operator i guess
110 | .mtk19 {
111 | color: $teal !important;
112 | }
113 | .mtk20 {
114 | color: #ff69b4 !important;
115 | }
116 | // strings
117 | .mtk21 {
118 | color: $green !important;
119 | }
120 | .mtk22 {
121 | color: #ff69b4 !important;
122 | }
123 | // functions
124 | .mtk23 {
125 | color: $blue !important;
126 | }
127 | // shell variables
128 | .mtk24 {
129 | color: $peach !important;
130 | }
131 | // weird variables
132 | .mtk25 {
133 | color: $pink !important;
134 | }
135 |
136 | // .bracket-highlighting-0 {
137 | // color: color.mix($text, $red, 40%) !important;
138 | // }
139 | // .bracket-highlighting-1 {
140 | // color: color.mix($text, $peach, 40%) !important;
141 | // }
142 | // .bracket-highlighting-2 {
143 | // color: color.mix($text, $yellow, 40%) !important;
144 | // }
145 | // .bracket-highlighting-3 {
146 | // color: color.mix($text, $green, 40%) !important;
147 | // }
148 | // .bracket-highlighting-4 {
149 | // color: color.mix($text, $blue, 40%) !important;
150 | // }
151 | // .bracket-highlighting-5 {
152 | // color: color.mix($text, $mauve, 40%) !important;
153 | // }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/themes/scss/theme/modules/index.scss:
--------------------------------------------------------------------------------
1 | @forward './repo';
2 | @forward './issues';
3 | @forward './chroma';
4 | @forward './codemirror';
5 | @forward './monaco';
6 | @forward './settings';
--------------------------------------------------------------------------------
/src/themes/scss/theme/modules/issues/_issue-sidebar.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/var' as var;
2 | @use '@lucas-labs/lui-micro/color' as color;
3 | @use '../../components/button' as button;
4 |
5 | @mixin sidebar {
6 | --spc: #{var.get('measure/1x')}; // var(--spc) 16px
7 | --spc-7_8: calc(var(--spc) * .875); // 7/8 of the base var(--spc-7_8) 14px
8 | --spc-6_8: calc(var(--spc) * .750); // 6/8 = 3/4 of the base var(--spc-6_8) 12px
9 | --spc-4_8: calc(var(--spc) * .500); // 4/8 = 1/2 of the base var(--spc-4_8) 8px
10 | --spc-3_8: calc(var(--spc) * .375); // 3/8 of the base var(--spc-3_8) 6px
11 | --spc-2_8: calc(var(--spc) * .250); // 2/8 = 1/4 of the base var(--spc-2_8) 4px
12 |
13 | display: flex;
14 | flex-direction: column;
15 | border: none !important;
16 | color: var(--color-text-light-2) !important;
17 | padding: 0 !important;
18 |
19 | // reset padding and margin for all children
20 | >*, .issue-sidebar-combo>*, >text+*, >.ui>* {
21 | margin: unset !important;
22 | padding: unset !important;
23 | font-size: var.get('font-size/sm') !important;
24 | }
25 |
26 | // alignment for main children elements
27 | >.ui, >.text, >.ui {
28 | padding-left: var(--spc-4_8) !important;
29 | padding-right: 0 !important;
30 | }
31 |
32 | .ui.ui.ui.compact.grid>.column:not(.row), .ui.ui.ui.compact.grid>.row>.column {
33 | padding-left: unset;
34 | padding-right: unset;
35 | }
36 |
37 | // reset for all actionable elements
38 | button, input, .dropdown, .ui.grid>.row>[class*="two wide"].column, .ui.grid>.column.row>[class*="two wide"].column, .ui.grid>[class*="two wide"].column, .ui.column.grid>[class*="two wide"].column {
39 | padding: 0 !important;
40 | margin: 0 !important;
41 | min-height: 0 !important;
42 | min-width: 0 !important;
43 | }
44 |
45 | // -------------------------------------------------------------- //
46 | // ---------------------- Elements Styling ---------------------- //
47 | // -------------------------------------------------------------- //
48 |
49 | a:hover {
50 | text-decoration: none !important;
51 | }
52 |
53 | // dividers
54 | >.divider {
55 | width: calc(100% - var(--spc-4_8)) !important;
56 | align-self: flex-end;
57 | margin: var(--spc-7_8) 0 !important;
58 | }
59 |
60 |
61 | // select branch dropdown
62 | .select-branch {
63 | align-self: flex-end;
64 |
65 | .branch-dropdown-button {
66 | @include button.hollow;
67 | }
68 |
69 | +.divider {
70 | border: none !important;
71 | margin: var(--spc-4_8) 0 !important;
72 | }
73 |
74 | .menu {
75 | --color-menu: #{color.get('elevation/3')};
76 |
77 | .branch-tag-item.active, .reference-list-menu, .reference-list-menu .item {
78 | --color-menu: #{color.get('elevation/4')} !important;
79 | }
80 |
81 | .branch-tag-item {
82 | --border-radius: #{var(--spc-4_8)};
83 |
84 | &:hover {
85 | color: currentColor !important;
86 | }
87 | }
88 | }
89 | }
90 |
91 | .issue-sidebar-combo {
92 | display: flex;
93 | flex-direction: column;
94 | gap: var(--spc-4_8) !important;
95 |
96 | > * {
97 | padding-left: var(--spc-4_8) !important;
98 | padding-right: var(--spc-4_8) !important;
99 | }
100 |
101 | .ui.dropdown {
102 | padding-top: var(--spc-3_8) !important;
103 | padding-bottom: var(--spc-3_8) !important;
104 | font-size: var.get('font-size/sm');
105 | transition: background-color .1s ease;
106 | border-radius: var(--border-radius) !important;
107 |
108 | >a {
109 | flex: 1;
110 | display: flex;
111 | justify-content: space-between;
112 |
113 | &:hover {
114 | color: currentColor !important;
115 | text-decoration: none !important;
116 | }
117 | }
118 |
119 | &:hover {
120 | background-color: rgba(#{color.get('elevation/6', 'rgb')}, .5);
121 | }
122 | }
123 | }
124 |
125 | // direct text children
126 | >.text {
127 | // background-color: orange !important;
128 | flex: 1;
129 | display: flex !important;
130 | justify-content: space-between;
131 | font-weight: 500 !important;
132 | margin-bottom: var(--spc-6_8) !important;
133 |
134 | strong {
135 | font-weight: 500 !important;
136 | }
137 | }
138 |
139 | .watching {
140 | display: flex;
141 | flex-direction: column;
142 | gap: var(--spc-6_8) !important;
143 |
144 | button {
145 | font-weight: 500;
146 | padding: var(--spc-4_8) var(--spc-6_8) !important;
147 | border-radius: var(--border-radius) !important;
148 |
149 | svg {
150 | color: color.get('subtle') !important;
151 | }
152 | }
153 | }
154 |
155 | .depending {
156 | // title
157 | >.text {
158 | display: inline-block;
159 | margin-bottom: var(--spc-6_8) !important;
160 | }
161 |
162 | >p {
163 | margin-bottom: var(--spc-4_8) !important;
164 | }
165 |
166 | .divided.list {
167 | display: flex;
168 | flex-direction: column;
169 | gap: var(--spc) !important;
170 |
171 | .dependency {
172 | border: none !important;
173 |
174 | .item-left {
175 | a { // issue
176 |
177 | }
178 |
179 | div.text { // repo
180 | font-size: var.get('font-size/xs') !important;
181 | }
182 | }
183 |
184 | // if not the first dependency in the list, we add a :before element to serve as divider
185 | &:not(:first-child) {
186 | position: relative;
187 | &:before {
188 | content: '';
189 | display: block;
190 | position: absolute;
191 | top: -8px;
192 | left: 0;
193 | height: 1px;
194 | width: 100%;
195 | background-color: color.get('elevation/4');
196 | }
197 | }
198 | }
199 | }
200 |
201 | #new-dependency-drop-list {
202 | padding: var(--spc-3_8) var(--spc-6_8) !important;
203 | // make radius to the right be 0 to merge with the add button
204 | border-top-right-radius: 0 !important;
205 | border-bottom-right-radius: 0 !important;
206 |
207 | &.active {
208 | // restore right border to 1px when active
209 | border-top-left-radius: var(--border-radius) !important;
210 | border-bottom-left-radius: var(--border-radius) !important;
211 | border-color: color.get('elevation/5') !important;
212 | outline: 2px solid var(--color-accent);
213 | }
214 |
215 | input {
216 | padding: var(--spc-3_8) var(--spc-6_8) !important;
217 | line-height: 20px !important;
218 | }
219 | }
220 |
221 | button {
222 | padding: var(--spc-4_8) !important;
223 | }
224 | }
225 |
226 | // reference
227 | div.ui.equal.width.compact.grid {
228 | button {
229 | @include button.hollow;
230 | display: flex;
231 | color: color.get('subtle') !important;
232 | width: unset !important;
233 | }
234 | }
235 |
236 | // issue due date form
237 | .issue-due-form {
238 | * {
239 | color: color.get('subtle') !important;
240 | }
241 |
242 | // input of type date
243 | input[type="date"] {
244 | padding: var(--spc-4_8) var(--spc-6_8) !important;
245 | border-top-left-radius: var(--border-radius);
246 | border-bottom-left-radius: var(--border-radius);
247 | }
248 |
249 | button {
250 | padding: var(--spc-4_8) !important;
251 | border-top-right-radius: var(--border-radius) !important;
252 | border-bottom-right-radius: var(--border-radius) !important;
253 | }
254 | }
255 |
256 | // direct form child
257 | >form, .form {
258 | display: flex;
259 | flex-direction: column;
260 | gap: var(--spc-4_8) !important;
261 | }
262 |
263 | // buttons at the end of the sidebar
264 | >form button, >button {
265 | font-size: var.get('font-size/sm') !important;
266 | border: none !important;
267 | background: transparent !important;
268 | padding: var(--spc-3_8) var(--spc-4_8) !important;
269 | justify-content: start !important;
270 | gap: var(--spc-4_8) !important;
271 | margin-bottom: var(--spc-2_8) !important;
272 | border-radius: var(--border-radius) !important;
273 |
274 | svg {
275 | color: color.get('subtle') !important;
276 | }
277 |
278 | &:hover {
279 | background: color.get('elevation/4') !important;
280 | }
281 |
282 | // if the data-modal attr is #sidebar-delete-issue, make the color red
283 | &[data-modal="#sidebar-delete-issue"] {
284 | color: color.get('palette/red/base') !important;
285 | svg {
286 | color: color.get('palette/red/base') !important;
287 | }
288 |
289 | &:hover {
290 | background: rgba(#{color.get('palette/red/dark/15%', 'rgb')}, 0.1) !important;
291 | }
292 | }
293 | }
294 | }
--------------------------------------------------------------------------------
/src/themes/scss/theme/modules/issues/_issue.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/var' as var;
2 | @use '@lucas-labs/lui-micro/color' as color;
3 | @use '../../components/button' as button;
4 | @use './issue-sidebar';
5 |
6 | @mixin apply-styles {
7 | .issue-content-right {
8 | @include issue-sidebar.sidebar;
9 | }
10 |
11 | .issue-title-header {
12 | margin-bottom: var.get('measure/1.5x');
13 | padding-bottom: var.get('measure/.75x');
14 | border-bottom: 1px solid color.get('elevation/5');
15 |
16 | .issue-title {
17 | .index {
18 | font-weight: 300;
19 | }
20 | }
21 |
22 | .issue-title-meta {
23 | color: color.get('subtle');
24 | }
25 |
26 | &:has(+ .ui.pull.tabs) { // if the next sibling is a tab (e.g. pull request tab selector)
27 | margin-bottom: var.get('measure/1x');
28 | padding-bottom: var.get('measure/.5x');
29 | border-bottom: none;
30 | }
31 | }
32 |
33 | .issue-content {
34 | column-gap: var.get('measure/1.5x');
35 | }
36 |
37 | .issue-content-left {
38 | .timeline-avatar img {
39 | border-radius: 50% !important;
40 | }
41 | }
42 |
43 | .timeline-item {
44 | &.event {
45 | .badge {
46 | // and not contains a tw-bg-* class
47 | &:not([class*="tw-bg-"]) {
48 | color: var(--color-timeline-badge-fg) !important;
49 | }
50 |
51 | &.tw-bg-green.tw-text-white {
52 | color: var(--color-green-contrast) !important;
53 | }
54 |
55 | &.tw-bg-red.tw-text-white {
56 | color: var(--color-red-contrast) !important;
57 | }
58 | }
59 |
60 | .ui.segments.conversation-holder {
61 | margin-left: var.get('measure/.5x');
62 | border-radius: var(--border-radius);
63 | }
64 | }
65 |
66 | &.comment {
67 | // comment form at the end
68 | &.form:not(.issue-content) {
69 | display: flex;
70 | align-items: flex-start;
71 | background-color: var(--color-body);
72 | gap: var.get('measure/1x');
73 | left: -68px !important;
74 | width: calc(100% + 68px - 16px) !important;
75 |
76 | @media (max-width: 768px) {
77 | left: 0 !important;
78 | margin-left: -16px !important;
79 | width: auto !important;
80 | }
81 |
82 | .timeline-avatar {
83 | display: block;
84 | position: relative !important;
85 | left: unset !important;
86 | flex: 0 0 auto !important;
87 | }
88 |
89 | .content {
90 | // fake title to mimic github new issue page
91 | &:before {
92 | display: block;
93 | content: 'Add a comment';
94 | font-weight: 600;
95 | margin-bottom: var.get('measure/1x');
96 | margin-top: var.get('measure/.5x');
97 | font-size: var.get('font-size/lg');
98 | }
99 |
100 | display: block;
101 | position: relative !important;
102 | margin-left: 0 !important;
103 | flex: 1;
104 |
105 | .ui.segment {
106 | padding: 0 !important;
107 | border: none !important;
108 | }
109 | }
110 | }
111 | }
112 | }
113 |
114 | .repository.view.issue {
115 | --avatar-size: 24px;
116 |
117 | .comment-list .ui.comments {
118 | background-color: transparent;
119 | gap: var.get('measure/1x');
120 | position: relative;
121 | }
122 |
123 | .comment-list {
124 | .ui.comment-code-cloud.segment {
125 | padding: 0 var.get('measure/.5x') var.get('measure/.5x') !important;
126 |
127 | .ui.comments {
128 | gap: var.get('measure/.5x');
129 |
130 | &:before {
131 | display: block;
132 | content: "";
133 | position: absolute;
134 | top: 0;
135 | bottom: 0;
136 | left: calc((var(--avatar-size) / 2) - 1px);
137 | top: 16px;
138 | width: 3px;
139 | background-color: var(--color-timeline);
140 | opacity: 50%;
141 | }
142 |
143 | .comment.code-comment .content.comment-container{
144 | background-color: transparent !important;
145 |
146 | .header.comment-header {
147 | .comment-header-left {
148 | gap: var.get('measure/.5x');
149 | .avatar img {
150 | z-index: 1 !important;
151 | width: var(--avatar-size) !important;
152 | height: var(--avatar-size) !important;
153 | }
154 | }
155 | }
156 | }
157 | }
158 |
159 | .code-comment-buttons {
160 | margin: 0!important;
161 | }
162 |
163 | .comment-form {
164 | margin-top: var.get('measure/1x');
165 |
166 | .field.footer {
167 | margin: 0 !important;
168 | padding: 0 !important;
169 |
170 | button {
171 | padding: var.get('measure/.5x') var.get('measure/1x') !important;
172 | }
173 | }
174 | }
175 | }
176 | }
177 | }
178 |
179 | .repository .ui.tabs.divider {
180 | margin-bottom: var.get('measure/1x');
181 | }
182 |
183 | .repository .diff-detail-box { // diff page detail box
184 | padding: var.get('measure/.5x') 0;
185 | height: 60px;
186 | }
187 |
188 | .repository.view.issue .pull.tabs.container {
189 | + div {
190 | >.diff-detail-box:first-child {
191 | margin: calc(var.get('measure/1x') * -1) 0 0 0;
192 | }
193 | }
194 | }
195 |
196 | .repository #diff-container {
197 | #diff-file-tree {
198 | top: 60px;
199 | .diff-file-tree-items {
200 | margin: 0;
201 | }
202 | }
203 |
204 | column-gap: var.get('measure/1x');
205 |
206 | .diff-file-body tr.tag-code:last-child td:first-child, .diff-file-body tr.tag-code:last-child td:first-child * {
207 | border-bottom-left-radius: calc(var(--border-radius) - 2px);
208 | }
209 |
210 | .diff-file-body tr.tag-code:last-child td:last-child, .diff-file-body tr.tag-code:last-child td:last-child * {
211 | border-bottom-right-radius: var(--border-radius);
212 | }
213 |
214 | .ui.attached.header.diff-file-header.sticky-2nd-row {
215 | position: sticky;
216 | top: 60px;
217 | z-index: 7;
218 | }
219 |
220 | .add-comment {
221 | border-top: 1px solid var(--color-secondary);
222 | border-bottom: 1px solid var(--color-secondary);
223 |
224 | .conversation-holder {
225 | .comment-code-cloud {
226 | padding: var.get('measure/.75x') !important;
227 | form {
228 | &:not(:first-child) {
229 | margin-top: var.get('measure/.5x');
230 | }
231 |
232 | .field.footer {
233 | margin: 0 !important;
234 | padding: 0 !important;
235 |
236 | button {
237 | padding: var.get('measure/.5x') var.get('measure/1x') !important;
238 | }
239 | }
240 | }
241 | }
242 | }
243 | }
244 | }
245 |
246 | // new issue page
247 | .page-content.repository.new.issue {
248 | .issue-content-left {
249 | .ui.comments .comment {
250 | .ui.segment.content {
251 | padding: 6px 0 0;
252 | border: none;
253 |
254 | .field {
255 | margin-bottom: var.get('measure/1x');
256 | }
257 |
258 | &:before, &:after {
259 | display: none;
260 | }
261 | }
262 | }
263 | }
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/src/themes/scss/theme/modules/issues/_list.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/var' as var;
2 | @use '@lucas-labs/lui-micro/color' as color;
3 |
4 | @mixin apply-styles {
5 | .page-content.repository.issue-list, .page-content.dashboard.issues {
6 | .secondary-nav {
7 | margin-bottom: var.get('measure/1.5x');
8 | }
9 |
10 | #issue-filters {
11 | margin: var.get('measure/2x') 0 var.get('measure/1x') 0;
12 | gap: var.get('measure/3x');
13 | align-items: center;
14 | }
15 |
16 | #issue-list {
17 | .flex-item {
18 | padding: var.get('measure/.75x') 0 var.get('measure/.75x') 0;
19 |
20 | &:first-child {
21 | padding-top: 0;
22 | }
23 |
24 | &:last-child {
25 | padding-bottom: 0;
26 | }
27 |
28 | .branch {
29 | background-color: color.get('elevation/1');
30 | }
31 |
32 | .flex-item-icon {
33 | svg {
34 | &.green {
35 | color: color.get('palette/green/base') !important;
36 | }
37 | }
38 | }
39 | }
40 | }
41 |
42 | .list-header {
43 | margin-bottom: var.get('measure/1.5x');
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/themes/scss/theme/modules/issues/index.scss:
--------------------------------------------------------------------------------
1 | @use './list';
2 | @use './issue';
3 |
4 | @mixin issues {
5 | @include list.apply-styles();
6 | @include issue.apply-styles();
7 | }
--------------------------------------------------------------------------------
/src/themes/scss/theme/modules/repo/_file-list.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/var' as var;
2 | @use '@lucas-labs/lui-micro/color' as color;
3 | @use '../../components/button' as button;
4 |
5 | @mixin apply-styles {
6 | .repo-button-row-left {
7 | gap: var.get('measure/.5x');
8 |
9 | .button {
10 | @include button.hollow;
11 | }
12 |
13 | .breadcrumb.repo-path {
14 | font-size: var.get('font-size/lg');
15 | padding: 0 0 0 var.get('measure/.5x');
16 |
17 | .section {
18 | &:first-of-type, &.active {
19 | font-weight: 600;
20 | }
21 | }
22 |
23 | .breadcrumb-divider, button {
24 | color: color.get('subtle') !important;
25 | }
26 |
27 | button {
28 | border-radius: var(--border-radius);
29 | padding: var.get('measure/.375x');
30 | margin: 0 0 0 var.get('measure/.5x') !important;
31 |
32 | svg {
33 | width: var.get('measure/1x');
34 | height: var.get('measure/1x');
35 | }
36 |
37 | &:hover {
38 | background-color: var(--color-nav-hover-bg);
39 | }
40 | }
41 | }
42 | }
43 |
44 | .repo-home-filelist {
45 | > div {
46 | margin: 0 !important;
47 | }
48 |
49 | display: flex;
50 | flex-direction: column;
51 | row-gap: var.get('measure/1x');
52 |
53 | // file list table
54 | #repo-files-table {
55 | // header
56 | .repo-file-last-commit {
57 | padding: var.get('measure/.75x') var.get('measure/1x') !important;
58 |
59 | .commit-summary {
60 | margin: 0;
61 | }
62 |
63 | .latest-commit {
64 | gap: var.get('measure/.5x');
65 |
66 | img {
67 | // make it a circle (avatar)
68 | border-radius: 50%;
69 | width: var.get('measure/1.25x');
70 | height: var.get('measure/1.25x');
71 | }
72 | }
73 |
74 | .label {
75 | // boton con icono y avatar
76 | .detail.icon.button img {
77 | display: none;
78 | }
79 | }
80 | }
81 |
82 | // cells
83 | .repo-file-cell {
84 | padding: var.get('measure/.5x') var.get('measure/1x') !important;
85 |
86 | &.name {
87 | display: flex;
88 | flex-wrap: nowrap;
89 | column-gap: var.get('measure/.5x');
90 | align-items: center;
91 | }
92 | }
93 | }
94 |
95 | // readme
96 | #readme {
97 |
98 | }
99 |
100 | .repository-summary .sub-menu .item {
101 | height: 30px;
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/themes/scss/theme/modules/repo/_home.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/var' as var;
2 | @use '@lucas-labs/lui-micro/color' as color;
3 |
4 | @mixin apply-styles {
5 | // description, labels, info
6 | .repo-home-sidebar-top {
7 | @media (max-width: 768px) {
8 | border-bottom: 1px solid color.get('elevation/6');
9 | margin-bottom: var.get('measure/1x');
10 | }
11 |
12 | form {
13 | margin-top: 0 !important;
14 | }
15 |
16 | #repo-topics, #topic_edit, .label-list {
17 | row-gap: var.get('measure/.5x');
18 | margin-top: 0 !important;
19 |
20 | .label:not(.basic) {
21 | font-size: var.get('font-size/sm') !important;
22 | background-color: rgba(#{color.get('palette/blue/base', 'rgb')}, 0.1);
23 | color: color.get('primary/base');
24 | border-radius: var.get('measure/2x');
25 | line-height: 22px !important;
26 | padding-top: 0 !important;
27 | padding-bottom: 0 !important;
28 |
29 | &:hover {
30 | background-color: rgba(#{color.get('palette/blue/base', 'rgb')}, 0.3) !important;
31 | color: color.get('primary/base') !important;
32 | }
33 | }
34 |
35 | .ui.selection.active.dropdown, .ui.selection.active.dropdown .menu {
36 | border-color: color.get('elevation/6')
37 | }
38 | }
39 |
40 | .repo-description {
41 | margin-bottom: var.get('measure/1x');
42 |
43 | +a.flex-text-block {
44 | svg {
45 | color: color.get('subtle') !important;
46 | }
47 | margin-bottom: var.get('measure/1x');
48 | }
49 | }
50 |
51 | .flex-text-block {
52 | gap: var.get('measure/.25x') !important;
53 | font-size: var.get('font-size/md') !important;
54 |
55 | svg {
56 | margin-right: var.get('measure/.25x') !important;
57 | }
58 | }
59 |
60 | @media (max-width: 768px) {
61 | .repo-description {
62 | margin-top: 0 !important;
63 |
64 | +a.flex-text-block {
65 | margin-bottom: 0;
66 | }
67 | }
68 |
69 | #repo-topics, #manage_topic, .flex-item-title {
70 | display: none;
71 | }
72 |
73 | .flex-item-body>div {
74 | flex: 1;
75 | }
76 | }
77 | }
78 |
79 | // releases + languages section
80 | .repo-home-sidebar-bottom {
81 | .flex-list .flex-item .flex-item-main {
82 | .flex-item {
83 | padding: 0 !important;
84 |
85 | .flex-item-main {
86 | gap: 0 !important;
87 |
88 | .flex-item-header {
89 | * {
90 | font-size: var.get('font-size/md') !important;
91 | }
92 |
93 | .flex-item-title {
94 | gap: var.get('measure/.5x') !important;
95 | .green.label {
96 | border-radius: var.get('measure/2x');
97 | font-size: var.get('font-size/sm') !important;
98 | background-color: transparent !important;
99 | border: 1px solid color.get('palette/green/base') !important;
100 | color: color.get('palette/green/base') !important;
101 | }
102 | }
103 | }
104 |
105 | .flex-item-body {
106 | .time {
107 | font-size: var.get('font-size/sm') !important;
108 | }
109 | }
110 | }
111 | }
112 | }
113 |
114 | // languages
115 | .flex-item-body {
116 | gap: 0 !important;
117 |
118 | .language-stats {
119 | margin: 0 !important;
120 | height: var.get('measure/.5x') !important;
121 | margin-bottom: var.get('measure/.5x') !important;
122 | }
123 |
124 | .language-stats-details {
125 | gap: var.get('measure/1x') !important;
126 | .item {
127 | font-size: var.get('font-size/sm') !important;
128 | padding: 0;
129 | gap: var.get('measure/.25x') !important;
130 |
131 | .color-icon {
132 | height: var.get('measure/.5x') !important;
133 | width: var.get('measure/.5x') !important;
134 | margin-right: var.get('measure/.25x') !important;
135 | }
136 | }
137 | }
138 | }
139 | }
140 |
141 | .repo-home-sidebar-bottom, .repo-home-sidebar-top {
142 | padding-left: var.get('measure/1.5x');
143 |
144 | @media (max-width: 768px) {
145 | padding-left: 0 !important;
146 | }
147 |
148 | .flex-item-title .item {
149 | display: flex;
150 | flex-wrap: nowrap;
151 | gap: var.get('measure/.5x');
152 | text-decoration: none;
153 |
154 | .small.label {
155 | background-color: color.get('elevation/7');
156 | border: 1px solid color.get('elevation/7');
157 | border-radius: var.get('measure/2x');
158 | color: color.get('text');
159 | font-weight: var(--base-text-weight-medium, 500);
160 | min-width: 20px;
161 | padding: 2px;
162 | text-align: center;
163 | align-items: center;
164 | justify-content: center;
165 | }
166 | }
167 |
168 | >.flex-list>.flex-item {
169 | padding-top: var.get('measure/1x');
170 | padding-bottom: var.get('measure/1x');
171 |
172 | >.flex-item-main {
173 | gap: var.get('measure/1x');
174 | }
175 | }
176 | }
177 |
178 | button.ui.primary.button.js-btn-clone-panel {
179 | display: flex;
180 | flex-wrap: nowrap;
181 | gap: var.get('measure/.5x');
182 | align-items: center;
183 |
184 | span {
185 | display: flex;
186 | flex-wrap: nowrap;
187 | gap: var.get('measure/.375x');
188 | align-items: center;
189 | }
190 | }
191 |
192 | .clone-panel-popup {
193 | .clone-panel-field {
194 | margin: var.get('measure/1x');
195 | }
196 |
197 | .clone-panel-list {
198 | margin: var.get('measure/1x');
199 |
200 | .item {
201 | margin: var.get('measure/.5x') 0;
202 | }
203 | }
204 |
205 | .clone-panel-tab {
206 | padding: 0 var.get('measure/1x') var.get('measure/.5x');
207 | display: flex;
208 | gap: var.get('measure/.5x');
209 |
210 | button {
211 | padding: var.get('measure/.375x') var.get('measure/.5x');
212 | font-weight: 600;
213 |
214 | &.active {
215 | border-bottom: 0;
216 | position: relative;
217 |
218 | &:after {
219 | content: '';
220 | display: block;
221 | position: absolute;
222 | bottom: -7px;
223 | left: 0;
224 | width: 100%;
225 | height: 2px;
226 | background-color: color.get('primary/base');
227 | }
228 | }
229 |
230 | &:hover {
231 | background-color: var(--color-hover);
232 | border-radius: var(--border-radius);
233 | transition: background 0.12s ease-out;
234 | text-decoration: none;
235 | }
236 | }
237 | }
238 | }
239 |
240 | .tippy-svg-arrow {
241 | display: none;
242 | }
243 |
244 | .repository.file.editor .commit-form-wrapper .commit-form:before, .repository.file.editor .commit-form-wrapper .commit-form:after {
245 | display: none;
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/src/themes/scss/theme/modules/repo/_secondary-navbar.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/var' as var;
2 | @use '@lucas-labs/lui-micro/color' as color;
3 | @use '../../components/button' as button;
4 |
5 | @mixin apply-styles {
6 | .secondary-nav {
7 | margin-bottom: var.get('measure/1.5x') !important;
8 |
9 | .ui.container {
10 | margin: 0 0 0 0 !important;
11 | max-width: unset !important;
12 | width: 100% !important;
13 | padding: 0 var.get('measure/1x');
14 | }
15 |
16 | // header
17 | .repo-header {
18 | margin: 0 0 var.get('measure/1x') 0;
19 |
20 | // repo image
21 | img.avatar {
22 | width: var.get('measure/2x') !important;
23 | height: var.get('measure/2x') !important;
24 | }
25 |
26 | // repo title (repo "owner / name" text)
27 | .flex-item-title {
28 | gap: var.get('measure/.25x') !important;
29 | font-size: var.get('font-size/md') !important;
30 | font-weight: normal !important;
31 | color: color.get('elevation/10') !important;
32 |
33 | a {
34 | padding: var.get('measure/.375x') var.get('measure/.5x') !important;
35 | border-radius: var.get('measure/.25x');
36 |
37 | &:hover {
38 | background-color: color.get('elevation/4') !important;
39 | color: color.get('text') !important;
40 | text-decoration: none !important;
41 | }
42 | }
43 |
44 | // repo name only
45 | a:last-child {
46 | font-weight: 600;
47 | }
48 | }
49 |
50 | // "public/private" label
51 | .flex-item-trailing {
52 | .label {
53 | padding: var.get('measure/.25x') var.get('measure/.5x');
54 | font-size: var.get('font-size/sm');
55 | border-radius: var.get('measure/1x');
56 | background-color: transparent;
57 | color: color.get('elevation/10');
58 | }
59 |
60 | // lock icon
61 | svg {
62 | color: color.get('elevation/10');
63 | }
64 | }
65 |
66 | @media (max-width: 768px) {
67 | // repo title (repo "owner / name" text)
68 | .flex-item-title {
69 | display: inline-block;
70 | white-space: nowrap;
71 | padding: 0 var.get('measure/.5x') !important;
72 | font-size: var.get('font-size/sm') !important;
73 | font-weight: 500 !important;
74 |
75 | a {
76 | padding: 0 !important;
77 |
78 | &:hover {
79 | background-color: transparent !important;
80 | color: color.get('primary/base') !important;
81 | text-decoration: none !important;
82 | }
83 | }
84 |
85 | a:first-child {
86 | float: left;
87 | margin-right: var.get('measure/.5x');
88 | color: color.get('elevation/10') !important;
89 | font-weight: 500 !important;
90 |
91 | &:hover {
92 | color: color.get('primary/base') !important;
93 | }
94 | }
95 |
96 | // repo name only
97 | a:last-child {
98 | font-size: var.get('font-size/md') !important;
99 | clear: left;
100 | display: block;
101 | }
102 | }
103 | }
104 | }
105 |
106 | .fork-flag {
107 | margin-top: calc(#{var.get('measure/1x')} * -1) !important;
108 | margin-bottom: var.get('measure/1x') !important;
109 | }
110 |
111 | // header navbar menu
112 | overflow-menu.ui.secondary.pointing.menu {
113 | .overflow-menu-items {
114 | gap: var.get('measure/.5x') !important;
115 | }
116 |
117 | // navbar menu items
118 | .item {
119 | padding: var.get('measure/.375x') var.get('measure/.5x') !important;
120 | margin-bottom: var.get('measure/.5x') !important;
121 | border-radius: var.get('measure/.25x');
122 | border: none !important;
123 |
124 | // icon
125 | svg {
126 | color: var(--color-text-light-3) !important;
127 | margin-right: var.get('measure/.5x') !important;
128 | }
129 |
130 | &:hover {
131 | background-color: var(--color-secondary-nav-hover-bg) !important;
132 | color: var(--color-text-light-2) !important;
133 | }
134 |
135 | // when the represents the current route
136 | &.active {
137 | border: none !important;
138 | &:after {
139 | content: '';
140 | display: block;
141 | position: absolute;
142 | width: 100%;
143 | height: 2px;
144 | left: 0;
145 | top: calc(100% + #{var.get('measure/.375x')} - 1px);
146 | background-color: color.get('primary/base');
147 | transform: unset;
148 | border: none;
149 | }
150 | }
151 |
152 | // numeric badge/label (e.g. indicating the number of issues)
153 | .small.label {
154 | background-color: color.get('elevation/7');
155 | border: 1px solid color.get('elevation/7');
156 | border-radius: var.get('measure/2x');
157 | color: color.get('text');
158 | font-weight: var(--base-text-weight-medium, 500);
159 | min-width: calc(var.get('measure/1x') + 1px);
160 | padding: 2px;
161 | text-align: center;
162 | align-items: center;
163 | justify-content: center;
164 | }
165 | }
166 | }
167 |
168 | // buttons at the right of the header (unwatch, star, fork, etc.)
169 | .repo-buttons {
170 | .button {
171 | font-size: var.get('font-size/sm') !important;
172 | @include button.hollow;
173 | }
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/themes/scss/theme/modules/repo/index.scss:
--------------------------------------------------------------------------------
1 | @use './home';
2 | @use './secondary-navbar';
3 | @use './file-list';
4 |
5 | @mixin repo {
6 | @include home.apply-styles();
7 | @include secondary-navbar.apply-styles();
8 | @include file-list.apply-styles();
9 | }
--------------------------------------------------------------------------------
/src/themes/scss/theme/modules/settings/_pages.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/var' as var;
2 | @use '@lucas-labs/lui-micro/color' as color;
3 | @use './section';
4 |
5 | @mixin settings-pages {
6 | .page-content.user.settings, .page-content.admin, .page-content.repository.settings {
7 | .ui.flex-container {
8 | column-gap: 40px;
9 |
10 | .flex-container-nav {
11 | width: 232px;
12 | @include section.nav;
13 | }
14 |
15 | .flex-container-main {
16 | @include section.content;
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/themes/scss/theme/modules/settings/index.scss:
--------------------------------------------------------------------------------
1 | @use './pages';
2 |
3 | @mixin settings {
4 | @include pages.settings-pages;
5 | }
--------------------------------------------------------------------------------
/src/themes/scss/theme/modules/settings/section/_content.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/var' as var;
2 | @use '@lucas-labs/lui-micro/color' as color;
3 |
4 | @mixin content {
5 | .ui.top.attached.header {
6 | background: unset;
7 | border: none;
8 | font-size: var.get('font-size/3xl');
9 | font-weight: 400;
10 | padding: 0 0 var.get('measure/.5x') 0;
11 | border-bottom: 1px solid var(--color-secondary);
12 | margin-bottom: var.get('measure/1x');
13 |
14 | // if not the first header, add a margin top
15 | &:not(:first-child) {
16 | margin-top: var.get('measure/2.5x');
17 | }
18 |
19 | &.error {
20 | border-color: var(--color-secondary) !important;
21 | background: unset !important;
22 | }
23 | }
24 |
25 | .ui.segment {
26 | border-radius: var(--border-radius);
27 |
28 | >*:last-child {
29 | margin-bottom: 0;
30 | padding-bottom: 0;
31 | }
32 | }
33 |
34 | .ui.attached.segment {
35 | background: unset;
36 | padding:0;
37 | border: none;
38 | margin: 0;
39 |
40 | &.bottom {
41 | margin-top: var.get('measure/2x');
42 | }
43 |
44 | a:not(.button) {
45 | color: var(--color-primary);
46 | text-decoration: underline;
47 | display: inline-flex;
48 | align-items: center;
49 | column-gap: var.get('measure/.375x');
50 |
51 | svg {
52 | margin-left: var.get('measure/.25x');
53 | }
54 | }
55 |
56 | .ui.form .field {
57 | margin: 0 0 1em;
58 | }
59 |
60 | .ui.form .fields .fields, .ui.form .field:last-child, .ui.form .fields:last-child .field {
61 | margin-bottom: 0;
62 | }
63 |
64 | .field {
65 | margin: var.get('measure/1x') 0;
66 | }
67 |
68 | .divider {
69 | margin: var.get('measure/1x') 0;
70 | }
71 |
72 | .list {
73 | border: 1px solid var(--color-secondary);
74 | border-radius: var(--border-radius);
75 |
76 | .item {
77 | margin-left: 0;
78 | margin-right: 0;
79 |
80 | &:not(:first-child), &:first-child {
81 | padding: 1rem;
82 | margin: 0rem 0rem 0rem 0rem;
83 | }
84 | }
85 | }
86 | }
87 | }
--------------------------------------------------------------------------------
/src/themes/scss/theme/modules/settings/section/_nav.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/var' as var;
2 | @use '@lucas-labs/lui-micro/color' as color;
3 |
4 | @mixin nav {
5 | .vertical.menu {
6 | --spacing: #{var.get('measure/.375x')} #{var.get('measure/.75x')};
7 | --min-height: #{var.get('measure/2x')};
8 | background-color: transparent;
9 | border: none;
10 |
11 | .header.item {
12 | display: flex;
13 | align-items: center;
14 | padding: var(--spacing);
15 | font-size: var.get('font-size/sm');
16 | font-weight: 600;
17 | color: color.get('subtle');
18 | background-color: transparent;
19 | min-height: var(--min-height);
20 | }
21 |
22 | a.item, details.item {
23 | min-height: var(--min-height);
24 | &:before {
25 | content: none;
26 | }
27 |
28 | &:not(:last-child) {
29 | margin-bottom: var.get('measure/.25x');
30 | }
31 |
32 | &.active {
33 | font-weight: 600;
34 | background-color: var(--color-menu);
35 | position: relative;
36 |
37 | &:hover {
38 | background-color: var(--color-hover);
39 | }
40 |
41 | &:after {
42 | background: var(--color-primary);
43 | border-radius: .375rem;
44 | content: "";
45 | height: 24px;
46 | width: 4px;
47 | position: absolute;
48 | top: 50%;
49 | transform: translateY(-50%);
50 | left: calc(0px - 4px - #{var.get('measure/.375x')});
51 | }
52 | }
53 | }
54 |
55 | a.item, details.item summary {
56 | padding: var(--spacing);
57 | border-radius: var(--border-radius);
58 | }
59 |
60 | details.item {
61 | --octicon-chevron-down: url('data:image/svg+xml;utf8,');
62 |
63 | summary {
64 | &:after {
65 | color: color.get('subtle');
66 | transform: scaleY(1);
67 | transition: transform .12s linear;
68 | mask-image: var(--octicon-chevron-down);
69 | -webkit-mask-image: var(--octicon-chevron-down);
70 | }
71 | }
72 |
73 | &[open] summary {
74 | &:after {
75 | transform: scaleY(-1);
76 | }
77 | }
78 |
79 | .menu {
80 | .item {
81 | display: flex;
82 | align-items: center;
83 | color: color.get('text') !important;
84 |
85 | &:hover {
86 | background-color: var(--color-hover);
87 | }
88 | }
89 |
90 | margin: 0 !important;
91 | }
92 | }
93 | }
94 | }
--------------------------------------------------------------------------------
/src/themes/scss/theme/modules/settings/section/index.scss:
--------------------------------------------------------------------------------
1 | @forward './nav';
2 | @forward './content';
--------------------------------------------------------------------------------
/src/themes/scss/theme/vars/_base.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/color' as color;
2 | @use '@lucas-labs/lui-micro/var' as var;
3 |
4 | @mixin base {
5 | :root {
6 | // ┌───────┐
7 | // │ fonts │
8 | // └───────┘
9 | --fonts-proportional: #{var.get('font-family')};
10 | --fonts-monospace: #{var.get('code-font-family')};
11 | --fonts-emoji: #{var.get('emoji-font-family')};
12 |
13 | // ┌───────┬───────────────────┐
14 | // │ fonts │ weights & heights │
15 | // └───────┴───────────────────┘
16 | --font-weight-light: 300;
17 | --font-weight-normal: 400;
18 | --font-weight-medium: 500;
19 | --font-weight-semibold: 600;
20 | --font-weight-bold: 700;
21 | --line-height-default: normal;
22 |
23 | // ┌────────┐
24 | // │ images │
25 | // └────────┘
26 | --checkbox-mask-checked: url('data:image/svg+xml;utf8,');
27 | --checkbox-mask-indeterminate: url('data:image/svg+xml;utf8,');
28 | --octicon-chevron-right: url('data:image/svg+xml;utf8,');
29 |
30 | // ┌────────┐
31 | // │ radius │
32 | // └────────┘
33 | --border-radius: #{var.get('measure/.5x')};
34 | --border-radius-medium: #{var.get('measure/1x')};
35 | --border-radius-full: calc(infinity * 1px);
36 |
37 | // ┌────────┐
38 | // │ others │
39 | // └────────┘
40 | --opacity-disabled: 0.55;
41 | --height-loading: 16rem;
42 | --min-height-textarea: 132px; // padding + 6 lines + border = calc(1.57142em + 6lh + 2px), but lh is not fully supported
43 | --checkbox-size: 16px; // height and width of checkbox and radio inputs
44 | --page-spacing: 24px; // space between page elements
45 | --page-margin-x: 32px; // minimum space on left and right side of page
46 | --tab-size: 4;
47 | }
48 |
49 | @media (min-width: 768px) and (max-width: 1200px) {
50 | :root {
51 | --page-margin-x: 24px;
52 | }
53 | }
54 |
55 | @media (max-width: 767.98px) {
56 | :root {
57 | --page-margin-x: 16px;
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/src/themes/scss/theme/vars/_colors.scss:
--------------------------------------------------------------------------------
1 | @use '@lucas-labs/lui-micro/var' as var;
2 | @use '@lucas-labs/lui-micro/color' as color;
3 |
4 | @mixin colors($is-dark: true) {
5 | $scheme: if($is-dark, 'dark', 'light');
6 | $scheme-inverted: if($is-dark, 'light', 'dark');
7 |
8 | :root {
9 | accent-color: var(--color-accent);
10 |
11 | @if $is-dark {
12 | color-scheme: dark;
13 | --is-dark-theme: true;
14 | } @else {
15 | color-scheme: light;
16 | --is-dark-theme: false;
17 | }
18 |
19 | // ┌─────────┐
20 | // │ primary │
21 | // └─────────┘
22 | --color-primary: #{color.get('primary/base')};
23 | --color-primary-contrast: #{color.get('primary/base', 'contrast')};
24 |
25 | // dark
26 | --color-primary-dark-1: #{color.get('primary/' + $scheme-inverted + '/3%')};
27 | --color-primary-dark-2: #{color.get('primary/' + $scheme-inverted + '/6%')};
28 | --color-primary-dark-3: #{color.get('primary/' + $scheme-inverted + '/9%')};
29 | --color-primary-dark-4: #{color.get('primary/' + $scheme-inverted + '/10%')};
30 | --color-primary-dark-5: #{color.get('primary/' + $scheme-inverted + '/20%')};
31 | --color-primary-dark-6: #{color.get('primary/' + $scheme-inverted + '/25%')};
32 | --color-primary-dark-7: #{color.get('primary/' + $scheme-inverted + '/30%')};
33 |
34 | // light
35 | --color-primary-light-1: #{color.get('primary/' + $scheme + '/3%')};
36 | --color-primary-light-2: #{color.get('primary/' + $scheme + '/6%')};
37 | --color-primary-light-3: #{color.get('primary/' + $scheme + '/10%')};
38 | --color-primary-light-4: #{color.get('primary/' + $scheme + '/15%')};
39 | --color-primary-light-5: #{color.get('primary/' + $scheme + '/20%')};
40 | --color-primary-light-6: #{color.get('primary/' + $scheme + '/30%')};
41 | --color-primary-light-7: #{color.get('primary/' + $scheme + '/40%')};
42 |
43 | // alpha
44 | --color-primary-alpha-10: rgba(#{color.get('primary/base', 'rgb')}, 0.1);
45 | --color-primary-alpha-20: rgba(#{color.get('primary/base', 'rgb')}, 0.2);
46 | --color-primary-alpha-30: rgba(#{color.get('primary/base', 'rgb')}, 0.3);
47 | --color-primary-alpha-40: rgba(#{color.get('primary/base', 'rgb')}, 0.4);
48 | --color-primary-alpha-50: rgba(#{color.get('primary/base', 'rgb')}, 0.5);
49 | --color-primary-alpha-60: rgba(#{color.get('primary/base', 'rgb')}, 0.6);
50 | --color-primary-alpha-70: rgba(#{color.get('primary/base', 'rgb')}, 0.7);
51 | --color-primary-alpha-80: rgba(#{color.get('primary/base', 'rgb')}, 0.8);
52 | --color-primary-alpha-90: rgba(#{color.get('primary/base', 'rgb')}, 0.9);
53 |
54 | // states
55 | --color-primary-hover: var(--color-primary-light-1);
56 | --color-primary-active: var(--color-primary-light-3);
57 |
58 | // ┌───────────┐
59 | // │ secondary │ » secondary
60 | // └───────────┘
61 | --color-secondary: #{color.get('secondary/base')};
62 |
63 | // dark
64 | --color-secondary-dark-1: #{color.get('secondary/' + $scheme-inverted + '/3%')};
65 | --color-secondary-dark-2: #{color.get('secondary/' + $scheme-inverted + '/6%')};
66 | --color-secondary-dark-3: #{color.get('secondary/' + $scheme-inverted + '/9%')};
67 | --color-secondary-dark-4: #{color.get('secondary/' + $scheme-inverted + '/12%')};
68 | --color-secondary-dark-5: #{color.get('secondary/' + $scheme-inverted + '/15%')};
69 | --color-secondary-dark-6: #{color.get('secondary/' + $scheme-inverted + '/20%')};
70 | --color-secondary-dark-7: #{color.get('secondary/' + $scheme-inverted + '/25%')};
71 | --color-secondary-dark-8: #{color.get('secondary/' + $scheme-inverted + '/30%')};
72 | --color-secondary-dark-9: #{color.get('secondary/' + $scheme-inverted + '/35%')};
73 | --color-secondary-dark-10: #{color.get('secondary/' + $scheme-inverted + '/40%')};
74 | --color-secondary-dark-11: #{color.get('secondary/' + $scheme-inverted + '/45%')};
75 | --color-secondary-dark-12: #{color.get('secondary/' + $scheme-inverted + '/50%')};
76 | --color-secondary-dark-13: #{color.get('secondary/' + $scheme-inverted + '/55%')};
77 |
78 | // light
79 | --color-secondary-light-1: #{color.get('secondary/' + $scheme + '/3%')};
80 | --color-secondary-light-2: #{color.get('secondary/' + $scheme + '/6%')};
81 | --color-secondary-light-3: #{color.get('secondary/' + $scheme + '/9%')};
82 | --color-secondary-light-4: #{color.get('secondary/' + $scheme + '/12%')};
83 |
84 | // alpha
85 | --color-secondary-alpha-10: rgba(#{color.get('secondary/base', 'rgb')}, 0.1);
86 | --color-secondary-alpha-20: rgba(#{color.get('secondary/base', 'rgb')}, 0.2);
87 | --color-secondary-alpha-30: rgba(#{color.get('secondary/base', 'rgb')}, 0.3);
88 | --color-secondary-alpha-40: rgba(#{color.get('secondary/base', 'rgb')}, 0.4);
89 | --color-secondary-alpha-50: rgba(#{color.get('secondary/base', 'rgb')}, 0.5);
90 | --color-secondary-alpha-60: rgba(#{color.get('secondary/base', 'rgb')}, 0.6);
91 | --color-secondary-alpha-70: rgba(#{color.get('secondary/base', 'rgb')}, 0.7);
92 | --color-secondary-alpha-80: rgba(#{color.get('secondary/base', 'rgb')}, 0.8);
93 | --color-secondary-alpha-90: rgba(#{color.get('secondary/base', 'rgb')}, 0.9);
94 |
95 | // states
96 | --color-secondary-button: var(--color-secondary-dark-4);
97 | --color-secondary-hover: var(--color-secondary-dark-3);
98 | --color-secondary-active: var(--color-secondary-dark-2);
99 |
100 | // ┌─────────┐
101 | // │ console │ » used for actions console and console files
102 | // └─────────┘
103 | --color-console-fg: #f7f8f9;
104 | --color-console-fg-subtle: #bdc4cc;
105 | --color-console-bg: #171b1e;
106 | --color-console-border: #2e353b;
107 | --color-console-hover-bg: #272d33;
108 | --color-console-active-bg: #2e353b;
109 | --color-console-menu-bg: #262b31;
110 | --color-console-menu-border: #414b55;
111 |
112 | // ┌───────┐
113 | // │ named │
114 | // └───────┘
115 | --color-red: #{color.get('palette/red/base')};
116 | --color-orange: #{color.get('palette/orange/base')};
117 | --color-yellow: #{color.get('palette/yellow/base')};
118 | --color-olive: #{color.get('palette/olive/base')};
119 | --color-green: #{color.get('palette/green/base')};
120 | --color-teal: #{color.get('palette/teal/base')};
121 | --color-blue: #{color.get('palette/blue/base')};
122 | --color-violet: #{color.get('palette/violet/base')};
123 | --color-purple: #{color.get('palette/purple/base')};
124 | --color-pink: #{color.get('palette/pink/base')};
125 | --color-brown: #{color.get('palette/brown/base')};
126 | --color-black: #{color.get('palette/black/base')};
127 |
128 | // ┌───────┐
129 | // │ named │ » contrasts (lugit addition)
130 | // └───────┘
131 | --color-green-contrast: #{if($is-dark, var(--color-black), var(--color-white))};
132 | --color-red-contrast: #{if($is-dark, var(--color-black), var(--color-white))};
133 |
134 |
135 | // ┌───────┐
136 | // │ named │ » light variants
137 | // └───────┘
138 | --color-red-light: #{color.get('palette/red/light/10%')};
139 | --color-orange-light: #{color.get('palette/orange/light/10%')};
140 | --color-yellow-light: #{color.get('palette/yellow/light/10%')};
141 | --color-olive-light: #{color.get('palette/olive/light/10%')};
142 | --color-green-light: #{color.get('palette/green/light/10%')};
143 | --color-teal-light: #{color.get('palette/teal/light/10%')};
144 | --color-blue-light: #{color.get('palette/blue/light/10%')};
145 | --color-violet-light: #{color.get('palette/violet/light/10%')};
146 | --color-purple-light: #{color.get('palette/purple/light/10%')};
147 | --color-pink-light: #{color.get('palette/pink/light/10%')};
148 | --color-brown-light: #{color.get('palette/brown/light/10%')};
149 | --color-black-light: #{if(
150 | $is-dark,
151 | color.get('palette/black/light/10%'),
152 | color.get('palette/white/light/10%')
153 | )};
154 |
155 | // ┌───────┐
156 | // │ named │ » dark 1 variants
157 | // └───────┘
158 | --color-red-dark-1: #{color.get('palette/red/dark/10%')};
159 | --color-orange-dark-1: #{color.get('palette/orange/dark/10%')};
160 | --color-yellow-dark-1: #{color.get('palette/yellow/dark/10%')};
161 | --color-olive-dark-1: #{color.get('palette/olive/dark/10%')};
162 | --color-green-dark-1: #{color.get('palette/green/dark/10%')};
163 | --color-teal-dark-1: #{color.get('palette/teal/dark/10%')};
164 | --color-blue-dark-1: #{color.get('palette/blue/dark/10%')};
165 | --color-violet-dark-1: #{color.get('palette/violet/dark/10%')};
166 | --color-purple-dark-1: #{color.get('palette/purple/dark/10%')};
167 | --color-pink-dark-1: #{color.get('palette/pink/dark/10%')};
168 | --color-brown-dark-1: #{color.get('palette/brown/dark/10%')};
169 | --color-black-dark-1: #{if(
170 | $is-dark,
171 | color.get('palette/black/dark/10%'),
172 | color.get('palette/white/dark/10%')
173 | )};
174 |
175 | // ┌───────┐
176 | // │ named │ » dark 2 variants
177 | // └───────┘
178 | --color-red-dark-2: #{color.get('palette/red/dark/20%')};
179 | --color-orange-dark-2: #{color.get('palette/orange/dark/20%')};
180 | --color-yellow-dark-2: #{color.get('palette/yellow/dark/20%')};
181 | --color-olive-dark-2: #{color.get('palette/olive/dark/20%')};
182 | --color-green-dark-2: #{color.get('palette/green/dark/20%')};
183 | --color-teal-dark-2: #{color.get('palette/teal/dark/20%')};
184 | --color-blue-dark-2: #{color.get('palette/blue/dark/20%')};
185 | --color-violet-dark-2: #{color.get('palette/violet/dark/20%')};
186 | --color-purple-dark-2: #{color.get('palette/purple/dark/20%')};
187 | --color-pink-dark-2: #{color.get('palette/pink/dark/20%')};
188 | --color-brown-dark-2: #{color.get('palette/brown/dark/20%')};
189 | --color-black-dark-2: #{if(
190 | $is-dark,
191 | color.get('palette/black/dark/20%'),
192 | color.get('palette/white/dark/20%')
193 | )};
194 |
195 | // ┌──────┐
196 | // │ ansi │ » for actions console and console files
197 | // └──────┘
198 | --color-ansi-black: #1e2327;
199 | --color-ansi-red: #cc4848;
200 | --color-ansi-green: #87ab63;
201 | --color-ansi-yellow: #cc9903;
202 | --color-ansi-blue: #3a8ac6;
203 | --color-ansi-magenta: #d22e8b;
204 | --color-ansi-cyan: #00918a;
205 | --color-ansi-white: var(--color-console-fg-subtle);
206 | --color-ansi-bright-black: #424851;
207 | --color-ansi-bright-red: #d15a5a;
208 | --color-ansi-bright-green: #93b373;
209 | --color-ansi-bright-yellow: #eaaf03;
210 | --color-ansi-bright-blue: #4e96cc;
211 | --color-ansi-bright-magenta: #d74397;
212 | --color-ansi-bright-cyan: #00b6ad;
213 | --color-ansi-bright-white: var(--color-console-fg);
214 |
215 | // ┌───────┐
216 | // │ other │
217 | // └───────┘
218 | --color-grey: #{color.get('elevation/9')};
219 | --color-grey-light: #{color.get('elevation/11')};
220 | --color-gold: #{color.get('palette/yellow/dark/20%')};
221 | --color-white: #{color.get('palette/white/base')};
222 | --color-git: #f05133;
223 |
224 | // ┌──────┐
225 | // │ diff │
226 | // └──────┘
227 | --color-diff-added-linenum-bg: #{if($is-dark, #{color.get('palette/teal/dark/50%')}, rgba(#{color.get('palette/white/base', 'rgb')}, .2))};
228 | --color-diff-added-row-bg: #{if($is-dark, #{color.get('palette/teal/dark/60%')}, rgba(#{color.get('palette/green/light/20%', 'rgb')}, .25))};
229 | --color-diff-added-row-border: #{if($is-dark, #{color.get('palette/teal/dark/40%')}, rgba(#{color.get('palette/green/light/25%', 'rgb')}, .25))};
230 | --color-diff-added-word-bg: #{if($is-dark, #{color.get('palette/teal/dark/50%')}, rgba(#{color.get('palette/green/light/6%', 'rgb')}, .3))};
231 | --color-diff-moved-row-bg: #{if($is-dark, #{color.get('palette/yellow/dark/75%')}, rgba(#{color.get('palette/yellow/light/20%', 'rgb')}, .25))};
232 | --color-diff-moved-row-border: #{if($is-dark, #{color.get('palette/yellow/dark/65%')}, rgba(#{color.get('palette/yellow/light/25%', 'rgb')}, .25))};
233 | --color-diff-removed-linenum-bg: #{if($is-dark, #{color.get('palette/red/dark/60%')}, rgba(#{color.get('palette/white/base', 'rgb')}, .2))};
234 | --color-diff-removed-row-bg: #{if($is-dark, #{color.get('palette/red/dark/65%')}, rgba(#{color.get('palette/red/light/30%', 'rgb')}, .15))};
235 | --color-diff-removed-row-border: #{if($is-dark, #{color.get('palette/red/dark/50%')}, rgba(#{color.get('palette/red/light/35%', 'rgb')}, .2))};
236 | --color-diff-removed-word-bg: #{if($is-dark, #{color.get('palette/red/dark/55%')}, rgba(#{color.get('palette/red/light/25%', 'rgb')}, .2))};
237 | --color-diff-inactive: #{if($is-dark, #{color.get('elevation/6')}, red)};
238 |
239 | // ┌────────┐
240 | // │ status │
241 | // └────────┘
242 | --color-error-border: #{color.get('palette/red/base')};
243 | --color-error-bg: rgba(#{color.get('palette/red/base', 'rgb')}, 0.15);
244 | --color-error-bg-active: #{color.get('palette/red/light/6%')};
245 | --color-error-bg-hover: #{color.get('palette/red/light/12%')};
246 | --color-error-text: #{color.get('palette/red/base')};
247 | --color-success-border: #{color.get('palette/green/light/12%')};
248 | --color-success-bg: rgba(#{color.get('palette/green/base', 'rgb')}, 0.15);
249 | --color-success-text: #{color.get('text')};
250 | --color-warning-border: #{color.get('palette/yellow/light/12%')};
251 | --color-warning-bg: #{color.get('palette/yellow/base')};
252 | --color-warning-text: #{color.get('elevation/3')};
253 | --color-info-border: #{color.get('palette/blue/light/12%')};
254 | --color-info-bg: #{color.get('elevation/3')};
255 | --color-info-text: #{color.get('text')};
256 |
257 | // ┌───────┐
258 | // │ badge │
259 | // └───────┘
260 | --color-red-badge: #{color.get('palette/red/dark/20%')};
261 | --color-red-badge-bg: rgba(#{color.get('palette/red/dark/20%', 'rgb')}, .1);
262 | --color-red-badge-hover-bg: rgba(#{color.get('palette/red/dark/20%', 'rgb')}, .2);
263 |
264 | --color-green-badge: #{color.get('palette/green/base')};
265 | --color-green-badge-bg: rgba(#{color.get('palette/green/base', 'rgb')}, .1);
266 | --color-green-badge-hover-bg: rgba(#{color.get('palette/green/base', 'rgb')}, .2);
267 |
268 | --color-yellow-badge: #{color.get('palette/yellow/dark/10%')};
269 | --color-yellow-badge-bg: rgba(#{color.get('palette/yellow/dark/10%', 'rgb')}, .1);
270 | --color-yellow-badge-hover-bg: rgba(#{color.get('palette/yellow/dark/10%', 'rgb')}, .2);
271 |
272 | --color-orange-badge: #{color.get('palette/orange/dark/10%')};
273 | --color-orange-badge-bg: rgba(#{color.get('palette/orange/dark/10%', 'rgb')}, .1);
274 | --color-orange-badge-hover-bg: rgba(#{color.get('palette/orange/dark/10%', 'rgb')}, .2);
275 |
276 | // ┌──────────────┐
277 | // │ target-based │
278 | // └──────────────┘
279 |
280 | // body colors
281 | --color-body: #{color.get('elevation/3')};
282 | --color-text-dark: #{color.get('text')};
283 | --color-text: #{color.get('text')};
284 | --color-text-light: #{color.get('subtle')};
285 | --color-text-light-1: #{color.get('subtle')};
286 | --color-text-light-2: #{color.get('subtle')};
287 | --color-text-light-3: #{color.get('subtle')};
288 | --color-footer: rgba(#{color.get('elevation/2', 'rgb')}, 0.2);
289 | --color-timeline: #{color.get('elevation/5')};
290 | --color-timeline-badge-fg: #{if($is-dark, var(--color-white), var.get('color-text'))};
291 |
292 | // box
293 | --color-box-header: #{rgba(color.get('elevation/4', 'rgb'), 1)};
294 | --color-box-body: #{color.get('elevation/3')};
295 | --color-box-body-highlight: #{color.get('elevation/4')};
296 |
297 | // input
298 | --color-input-text: var(--color-text-dark);
299 | --color-input-background: #{color.get('elevation/4')};
300 | --color-input-toggle-background: #{color.get('elevation/4')};
301 | --color-input-border: #{color.get('elevation/5')};
302 | --color-input-border-hover: #{color.get('elevation/6')};
303 |
304 | // light
305 | --color-light: #00001728;
306 | --color-light-mimic-enabled: rgba(0, 0, 0, calc(40 / 255 * 222 / 255 / var(--opacity-disabled)));
307 | --color-light-border: #{color.get('elevation/6')};
308 | --color-hover: rgba(#{color.get('elevation/6', 'rgb')}, 0.5);
309 | --color-hover-opaque: #{color.get('elevation/6', 'rgb')};
310 | --color-active: #{color.get('elevation/5')};
311 | --color-menu: #{color.get('elevation/4')};
312 | --color-card: #{color.get('elevation/3')};
313 | --color-markup-table-row: rgba(#{color.get('text', 'rgb')}, 0.02);
314 | --color-markup-code-block: #{color.get('elevation/4')};
315 | --color-markup-code-inline: #{color.get('elevation/4')};
316 | --color-button: #{color.get('elevation/4')};
317 | --color-code-bg: #{color.get('elevation/3')};
318 | --color-shadow: #{if($is-dark, rgba(#{color.get('elevation/1', 'rgb')}, 0.5), rgba(#{color.get('elevation/6', 'rgb')}, 0.5))};
319 | --color-shadow-opaque: #{if($is-dark, color.get('elevation/1'), color.get('elevation/6'))};
320 |
321 | --color-shadow-rgb: #{if($is-dark, color.get('elevation/1', 'rgb'), color.get('elevation/6', 'rgb'))};
322 |
323 | --color-secondary-bg: #{color.get('elevation/4')};
324 | --color-expand-button: #{color.get('elevation/6')};
325 | --color-placeholder-text: #{color.get('elevation/9')};
326 | --color-editor-line-highlight: var(--color-primary-light-5);
327 | --color-project-column-bg: #{color.get('elevation/1')};
328 | --color-caret: var(--color-text);
329 | --color-reaction-bg: rgba(#{color.get('primary/dark/30%', 'rgb')}, .1);;
330 | --color-reaction-hover-bg: rgba(#{color.get('primary/dark/30%', 'rgb')}, .2);;
331 | --color-reaction-active-bg: rgba(#{color.get('primary/dark/30%', 'rgb')}, .05);;
332 | --color-tooltip-text: #{if($is-dark, color.get('text'), color.get('elevation/1'))};
333 | --color-tooltip-bg: #{if($is-dark, color.get('elevation/7'), color.get('elevation/10'))};
334 | --color-overlay-backdrop: rgba(#{color.get('elevation/1', 'rgb')}, 0.8);
335 |
336 | // navbar
337 | --color-nav-bg: #{color.get('navbar/bg', $default: if($is-dark, color.get('elevation/1'), color.get('elevation/4')))};
338 | --color-nav-hover-bg: #{color.get('navbar/hover', $default: if($is-dark, color.get('elevation/5'), color.get('elevation/5')))};
339 | --color-nav-text: #{color.get('navbar/fg', $default: var(--color-text))};
340 | --color-secondary-nav-bg: #{if($is-dark, color.get('elevation/1'), color.get('elevation/4'))};
341 | --color-secondary-nav-hover-bg: #{if($is-dark, color.get('elevation/4'), color.get('elevation/5'))};
342 |
343 | // label
344 | --color-label-text: var(--color-text);
345 | --color-label-bg: #7282924b;
346 | --color-label-hover-bg: #728292a0;
347 | --color-label-active-bg: #728292ff;
348 |
349 | // accents
350 | --color-accent: #{color.get('accent', $default: color.get('palette/blue/dark/25%'))};
351 | --color-small-accent: #{color.get('accent', $default: var(--color-primary-light-5))};
352 | --color-highlight-fg: #{color.get('palette/yellow/dark/20%')};
353 | --color-highlight-bg: #{color.get('palette/yellow/dark/75%')};
354 | }
355 |
356 | #commits-table {
357 | // even rows color
358 | --color-light: transparent !important;
359 | }
360 |
361 | #navbar {
362 | #navbar-logo {
363 | img {
364 | height: var.get('navbar-logo/height') !important;
365 | width: var.get('navbar-logo/width') !important;
366 | }
367 | }
368 | }
369 |
370 | .markup {
371 | code {
372 | border-radius: var.get('measure/.375x') !important;
373 | }
374 |
375 | p {
376 | line-height: 1.5 !important;
377 | }
378 | }
379 |
380 | ::selection {
381 | background-color: #{color.get('primary/dark/60%')};
382 | color: #{color.get('primary/dark/60%', 'contrast')};
383 | }
384 |
385 | // // In dark mode, invert emojis that are hard to read otherwise
386 | @if $is-dark {
387 | .emoji[aria-label="check mark"],
388 | .emoji[aria-label="currency exchange"],
389 | .emoji[aria-label="TOP arrow"],
390 | .emoji[aria-label="END arrow"],
391 | .emoji[aria-label="ON! arrow"],
392 | .emoji[aria-label="SOON arrow"],
393 | .emoji[aria-label="heavy dollar sign"],
394 | .emoji[aria-label="copyright"],
395 | .emoji[aria-label="registered"],
396 | .emoji[aria-label="trade mark"],
397 | .emoji[aria-label="multiply"],
398 | .emoji[aria-label="plus"],
399 | .emoji[aria-label="minus"],
400 | .emoji[aria-label="divide"],
401 | .emoji[aria-label="curly loop"],
402 | .emoji[aria-label="double curly loop"],
403 | .emoji[aria-label="wavy dash"],
404 | .emoji[aria-label="paw prints"],
405 | .emoji[aria-label="musical note"],
406 | .emoji[aria-label="musical notes"] {
407 | filter: invert(100%) hue-rotate(180deg);
408 | }
409 | }
410 | }
411 |
--------------------------------------------------------------------------------
/src/themes/scss/theme/vars/index.scss:
--------------------------------------------------------------------------------
1 | @forward './base';
2 | @forward './colors';
--------------------------------------------------------------------------------
/tools/build.js:
--------------------------------------------------------------------------------
1 | import { join } from 'path';
2 | import { cwd } from 'process';
3 | import { mkdirSync, rmSync, existsSync } from 'fs';
4 | import { buildScss } from './tasks/scss.js';
5 | import { buildImg } from './tasks/img.js';
6 | import { buildTemplates } from './tasks/templates.js';
7 | import { Logger } from './utils/logger.js';
8 | import { buildFonts } from './tasks/fonts.js';
9 | import { optimizeCss } from './tasks/optimize-css.js';
10 | const srcPath = join(cwd(), 'src');
11 | const distPath = join(cwd(), 'dist');
12 |
13 | const logger = new Logger('build', 'info', 'brightMagenta');
14 |
15 | function exit(err) {
16 | if (err) {
17 | console.error(err);
18 | } else {
19 | console.log('')
20 | logger.info('Build completed successfully');
21 | }
22 |
23 | process.exit(err ? 1 : 0);
24 | }
25 |
26 | async function build() {
27 | // cleaning, remove dist folder
28 | if (existsSync(distPath)) {
29 | rmSync(distPath, { recursive: true });
30 | }
31 |
32 | // recreate dist folder
33 | mkdirSync(distPath, { recursive: true });
34 |
35 | // start building tasks
36 | await Promise.all([
37 | buildScss(srcPath, distPath).then(() => optimizeCss(distPath)),
38 | buildImg(srcPath, distPath),
39 | buildFonts(srcPath, distPath),
40 | buildTemplates(srcPath, distPath),
41 | ]);
42 | }
43 |
44 | build().then(exit).catch(exit);
45 |
--------------------------------------------------------------------------------
/tools/deploy.js:
--------------------------------------------------------------------------------
1 | import { Logger } from './utils/logger.js';
2 | import { getArg } from './utils/funcs.js';
3 | import { resolve } from 'path';
4 | import { cwd } from 'process';
5 | import { deploy } from './tasks/deploy.js';
6 |
7 | const logger = new Logger('deploy', 'info', 'brightMagenta');
8 |
9 | const src = './src';
10 | const dist = './dist';
11 | const serviceName = getArg('--service', 'gitea');
12 | const srcPath = resolve(cwd(), src);
13 | const distPath = resolve(cwd(), dist);
14 | const serverPath = resolve(
15 | cwd(),
16 | getArg('--server', 'd:/users/lucas/Desktop/dev/server/gitea')
17 | );
18 |
19 | logger.info('Deploy started!');
20 | logger.info(`Service name: '${serviceName}'`);
21 | logger.info(`Src path: ${srcPath}`);
22 | logger.info(`Dist path: ${distPath}`);
23 | logger.info(`Server path: ${serverPath}`);
24 |
25 | function exit(err) {
26 | err && logger.error(err);
27 | process.exit(err ? 1 : 0);
28 | }
29 |
30 | async function executeDeployTask() {
31 | await deploy(srcPath, distPath, serverPath, serviceName);
32 | }
33 |
34 | executeDeployTask().then(exit).catch(exit);
35 |
--------------------------------------------------------------------------------
/tools/minimize-css.js:
--------------------------------------------------------------------------------
1 | import { optimizeCss } from "./tasks/optimize-css.js";
2 | import { cwd } from 'process';
3 | import { join } from 'path';
4 |
5 | const distPath = join(cwd(), 'dist');
6 |
7 | // run the optimization creating a .min.css file for each .css file in the dist folder
8 | // useful to check issues with the css files optimization
9 | // (in order to use this script, you need to first build the project removing the
10 | // optimization step from the build.js file)
11 | optimizeCss(distPath, false).then(() => {
12 | console.log('Optimization completed');
13 | }).catch((err) => {
14 | console.error(err);
15 | });
16 |
17 |
--------------------------------------------------------------------------------
/tools/restart.js:
--------------------------------------------------------------------------------
1 | import { getArg } from './utils/funcs.js';
2 | import { restartService } from './tasks/restart-service.js';
3 | import { Logger } from './utils/logger.js';
4 |
5 | const logger = new Logger('restart', 'info', 'brightMagenta');
6 | const serviceName = getArg('--service', 'gitea');
7 |
8 | function exit(err) {
9 | if (err) {
10 | console.error(err);
11 | } else {
12 | console.log('');
13 | logger.info('Build completed successfully');
14 | }
15 |
16 | process.exit(err ? 1 : 0);
17 | }
18 |
19 | restartService(serviceName).then(exit).catch(exit);
20 |
--------------------------------------------------------------------------------
/tools/serve.js:
--------------------------------------------------------------------------------
1 | import { watch } from 'chokidar';
2 | import { TaskDebouncer } from './utils/task-debouncer.js';
3 | import { Logger } from './utils/logger.js';
4 | import { getArg } from './utils/funcs.js';
5 | import { resolve } from 'path';
6 | import { cwd } from 'process';
7 | import { deploy } from './tasks/deploy.js';
8 |
9 | const src = './src';
10 | const dist = './dist';
11 | const serviceName = getArg('--service', 'gitea');
12 | const srcPath = resolve(cwd(), src);
13 | const distPath = resolve(cwd(), dist);
14 | const serverPath = resolve(
15 | cwd(),
16 | getArg('--server', 'd:/users/lucas/Desktop/dev/server/gitea')
17 | );
18 | const debouncer = new TaskDebouncer(300);
19 | const logger = new Logger('serve', 'info', 'brightMagenta');
20 |
21 | logger.info('Serve task started!');
22 | logger.info('Watching for changes...');
23 | logger.info(`Service name: '${serviceName}'`);
24 | logger.info(`Src path: ${srcPath}`);
25 | logger.info(`Dist path: ${distPath}`);
26 | logger.info(`Server path: ${serverPath}`);
27 |
28 | const watcher = watch([`${src}/**/*`], {
29 | persistent: true,
30 | ignoreInitial: true,
31 | });
32 |
33 | watcher.on('change', (file) => debouncer.add(
34 | deploy,
35 | srcPath,
36 | distPath,
37 | serverPath,
38 | serviceName,
39 | file,
40 | true
41 | ));
42 |
--------------------------------------------------------------------------------
/tools/tasks/copy-to.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 | import { Logger } from '../utils/logger.js';
4 |
5 | const logger = new Logger(copyTo.name, 'info', 'brightYellow');
6 |
7 | export async function copyTo(sourcePath, targetPath) {
8 | logger.info(`Copying ${sourcePath} to ${targetPath}`);
9 | await recursiveCopy(sourcePath, targetPath);
10 | logger.info(`Copy has finished!`);
11 | }
12 |
13 | async function recursiveCopy(sourcePath, targetPath) {
14 | // Create the target directory if it doesn't exist
15 | if (!fs.existsSync(targetPath)) {
16 | fs.mkdirSync(targetPath, { recursive: true });
17 | }
18 |
19 | // Get all files and directories in the source path
20 | const files = fs.readdirSync(sourcePath, { withFileTypes: true });
21 |
22 | for (const file of files) {
23 | const sourceFile = path.join(sourcePath, file.name);
24 | const targetFile = path.join(targetPath, file.name);
25 |
26 | if (file.isDirectory()) {
27 | // Recursively copy directories
28 | await recursiveCopy(sourceFile, targetFile);
29 | } else {
30 | // Copy files
31 | fs.copyFileSync(sourceFile, targetFile);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tools/tasks/deploy.js:
--------------------------------------------------------------------------------
1 | import { Logger } from '../utils/logger.js';
2 | import { buildScss } from './scss.js';
3 | import { buildFonts } from './fonts.js';
4 | import { buildTemplates } from './templates.js';
5 | import { copyTo } from './copy-to.js';
6 | import { restartService } from './restart-service.js';
7 | import { extname } from 'path';
8 | import browsersync from 'browser-sync';
9 | import { optimizeCss } from './optimize-css.js';
10 |
11 | const logger = new Logger('deploy', 'info', 'brightMagenta');
12 |
13 | const sync = browsersync.create('lugit')
14 |
15 | export async function deploy(srcPath, distPath, serverPath, serviceName, file = null, live = false) {
16 | logger.info('Deploying...');
17 |
18 | if(live && !sync.active) {
19 | sync.init({
20 | proxy: 'http://lugit.local',
21 | port: 8080,
22 | })
23 | }
24 |
25 | let shouldRestart = true;
26 |
27 | // check if it's an scss
28 | if (file !== null && file !== undefined && extname(file) === '.scss') {
29 | shouldRestart = false;
30 | }
31 |
32 | try {
33 | await buildScss(srcPath, distPath).then(() => optimizeCss(distPath));
34 | await buildFonts(srcPath, distPath);
35 | // await buildImg(srcPath, distPath);
36 | await buildTemplates(srcPath, distPath);
37 | await copyTo(distPath, serverPath);
38 | shouldRestart && await restartService(serviceName);
39 |
40 | if(!shouldRestart && live) {
41 | sync.reload();
42 | }
43 |
44 | logger.info('Deployment successful!');
45 | } catch (error) {
46 | logger.error(`Deployment failed: ${error}`);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tools/tasks/fonts.js:
--------------------------------------------------------------------------------
1 | import { copyFileSync, mkdirSync, existsSync } from 'fs';
2 | import { join } from 'path';
3 | import { readFiles } from '../utils/funcs.js';
4 | import { Logger } from '../utils/logger.js';
5 |
6 | const logger = new Logger(buildFonts.name, 'info', 'brightCyan');
7 | const imgSrc = 'themes/fonts';
8 | const imgDest = '/public/assets/fonts';
9 |
10 | export async function buildFonts(srcHome, distHome) {
11 | logger.info('Fonts build has started');
12 | const fontsSrcPath = join(srcHome, imgSrc);
13 | const fontsDestPath = join(distHome, imgDest);
14 |
15 | // if fontsSrcPath does not exist, return
16 | if (!existsSync(fontsSrcPath)) {
17 | logger.warn(`No fonts found in ${fontsSrcPath} (there's not even a folder there)`);
18 | return;
19 | }
20 |
21 | const files = readFiles(fontsSrcPath, [
22 | '.woff',
23 | '.woff2',
24 | '.ttf',
25 | '.eot',
26 | '.svg',
27 | '.otf',
28 | ]);
29 |
30 | // if there are no files, return
31 | if (!files.length) {
32 | logger.warn(`No fonts found in ${fontsSrcPath}`);
33 | return;
34 | }
35 |
36 | mkdirSync(fontsDestPath, { recursive: true });
37 |
38 | for (const file of files) {
39 | // just copy the file
40 | copyFileSync(join(fontsSrcPath, file), join(fontsDestPath, file));
41 | }
42 |
43 | logger.info('Fonts build has finished');
44 | }
45 |
--------------------------------------------------------------------------------
/tools/tasks/img.js:
--------------------------------------------------------------------------------
1 | import { fabric } from 'fabric';
2 | import imageminZopfli from 'imagemin-zopfli';
3 | import { readFile, writeFile } from 'node:fs/promises';
4 | import { join, basename } from 'path';
5 | import { optimize } from 'svgo';
6 | import { readFiles } from '../utils/funcs.js';
7 | import { Logger } from '../utils/logger.js';
8 | import { mkdirSync, copyFileSync } from 'fs';
9 |
10 | const logger = new Logger(buildImg.name, 'info', 'brightGreen');
11 | const imgSrc = 'themes/img';
12 | const imgDest = '/public/assets/img';
13 |
14 | export async function buildImg(srcHome, distHome) {
15 | logger.info('Images build has started');
16 | const imgSrcPath = join(srcHome, imgSrc);
17 | const imgDestPath = join(distHome, imgDest);
18 | const images = { logos: { logo: undefined, favicon: undefined }, others: [] };
19 | mkdirSync(imgDestPath, { recursive: true });
20 |
21 | const files = readFiles(imgSrcPath, ['.svg', '.png', '.jpg', '.webp', '.gif']);
22 |
23 | // Separate logo.svg and favicon.svg from the rest
24 | files.forEach((file) => {
25 | if (file === 'logo.svg') {
26 | images.logos.logo = join(imgSrcPath, file);
27 | } else if (file === 'favicon.svg') {
28 | images.logos.favicon = join(imgSrcPath, file);
29 | } else {
30 | images.others.push(join(imgSrcPath, file));
31 | }
32 | });
33 |
34 | await Promise.all([
35 | processLogos(images.logos, imgDestPath),
36 | processOthers(images.others, imgDestPath),
37 | ])
38 |
39 | logger.info('Images build has finished');
40 | }
41 |
42 | async function processLogos(logos, distHome) {
43 | const promises = [];
44 |
45 | if (logos.logo) {
46 | const svg = await readFile(logos.logo, 'utf8');
47 | promises.push(generate(svg, join(distHome, 'logo.svg'), { size: 32 }));
48 | promises.push(generate(svg, join(distHome, 'logo.png'), { size: 512 }));
49 | }
50 |
51 | if (logos.favicon) {
52 | const svg = await readFile(logos.favicon, 'utf8');
53 | promises.push(
54 | generate(svg, join(distHome, 'favicon.svg'), { size: 32 }),
55 | generate(svg, join(distHome, 'favicon.png'), { size: 180 }),
56 | generate(svg, join(distHome, 'apple-touch-icon.png'), { size: 180, bg: true }),
57 | generate(svg, join(distHome, 'avatar_default.png'), { size: 200, bg: true })
58 | );
59 | }
60 |
61 | await Promise.all(promises);
62 | }
63 |
64 | function loadSvg(svg) {
65 | return new Promise((resolve) => {
66 | fabric.loadSVGFromString(svg, (objects, options) => {
67 | resolve({ objects, options });
68 | });
69 | });
70 | }
71 |
72 | async function generate(svg, path, { size, bg }) {
73 | if (String(path).endsWith('.svg')) {
74 | const { data } = optimize(svg, {
75 | plugins: [
76 | 'preset-default',
77 | 'removeDimensions',
78 | {
79 | name: 'addAttributesToSVGElement',
80 | params: { attributes: [{ height: size }] },
81 | },
82 | ],
83 | });
84 | await writeFile(path, data);
85 | return;
86 | }
87 |
88 | const { objects, options } = await loadSvg(svg);
89 | const canvas = new fabric.Canvas();
90 |
91 |
92 | const newWidth = size * options.width / options.height;
93 | canvas.setDimensions({ width: newWidth, height: size });
94 | const ctx = canvas.getContext('2d');
95 | ctx.scale(
96 | options.width ? newWidth / options.width : 1,
97 | options.height ? size / options.height : 1
98 | );
99 |
100 | if (bg) {
101 | canvas.add(
102 | new fabric.Rect({
103 | left: 0,
104 | top: 0,
105 | height: size * (1 / (size / options.height)),
106 | width: size * (1 / (size / options.width)),
107 | fill: 'black',
108 | })
109 | );
110 | }
111 |
112 | canvas.add(fabric.util.groupSVGElements(objects, options));
113 | canvas.renderAll();
114 |
115 | let png = Buffer.from([]);
116 | for await (const chunk of canvas.createPNGStream()) {
117 | png = Buffer.concat([png, chunk]);
118 | }
119 |
120 | png = await imageminZopfli({ more: true })(png);
121 | await writeFile(path, png);
122 | }
123 |
124 | async function processOthers(others, distHome) {
125 | // just copy the rest of the images to dist
126 | for (const img of others) {
127 | copyFileSync(img, join(distHome, basename(img)));
128 | }
129 | }
--------------------------------------------------------------------------------
/tools/tasks/optimize-css.js:
--------------------------------------------------------------------------------
1 | import { join } from 'path';
2 | import { Logger } from '../utils/logger.js';
3 | import { readFileSync, readdirSync } from 'fs';
4 | import * as csstree from 'css-tree';
5 | import { writeFile } from 'fs/promises';
6 |
7 | const logger = new Logger('build', 'info', 'brightMagenta');
8 |
9 | function getcCssFiles(distPath, path) {
10 | try {
11 | return readdirSync(join(distPath, path))
12 | .filter((fn) => fn.endsWith('.css') && !fn.endsWith('.min.css'))
13 | .map((file) => ({
14 | name: file.replace('.css', ''),
15 | path: join(distPath, path, file),
16 | }));
17 | } catch (err) {
18 | return [];
19 | }
20 | }
21 |
22 | export async function optimizeCss(distPath, replace = true) {
23 | // get the css files in the dist folder
24 | const cssFiles = getcCssFiles(distPath, 'public/assets/css');
25 |
26 | for (const file of cssFiles) {
27 | logger.info(`Sanitizing ${file.name} css file`);
28 | let usedCssVariables = [];
29 |
30 | // read the css file
31 | const cssContent = readFileSync(file.path, { encoding: 'utf-8' });
32 | const ast = csstree.parse(cssContent, {});
33 |
34 | // get the css variables used in the css file
35 | csstree.walk(ast, (node) => {
36 | if (node.type === 'Function' && node.name === 'var') {
37 | // get the variable name
38 | for (const child of node.children) {
39 | if (child.type === 'Identifier') {
40 | usedCssVariables.push(child.name);
41 | }
42 | }
43 | }
44 |
45 | // could also be that the variable is assigned to another variable
46 | if (node.type === 'Declaration' && node.property.startsWith('--')) {
47 | // check if its assigned to another variable
48 | if(node.property.startsWith('--')) {
49 | if (node.value && node.value.type === 'Function' && node.value.name === 'var') {
50 | for (const child of node.value.children) {
51 | if (child.type === 'Identifier') {
52 | if (!usedCssVariables.includes(child.name)) {
53 | usedCssVariables.push(child.name);
54 | }
55 | }
56 | }
57 | } else if (node.value && node.value.type === 'Raw' && node.value.value) {
58 | const val = node.value.value.trimStart(); // var(--v-primary)
59 |
60 | // if starts with var(, then its assigned to another variable or many variables, get everything that's inside
61 | // var(...) using regex capturing
62 |
63 | // get all varname groups
64 | const matches = val.matchAll(/var\((?[^),\s]+)/g);
65 |
66 | for (const match of matches) {
67 | // get the varname group
68 | const varname = match.groups?.varname;
69 |
70 | if (varname) {
71 | if (!usedCssVariables.includes(varname)) {
72 | usedCssVariables.push(varname);
73 | }
74 | }
75 | }
76 | }
77 | }
78 |
79 | if (node.value.children) {
80 | for (const child of node.value.children) {
81 | if (child.type === 'Function' && child.name === 'var') {
82 | for (const varChild of child.children) {
83 | if (varChild.type === 'Identifier') {
84 | usedCssVariables.push(varChild.name);
85 | }
86 | }
87 | }
88 | }
89 | }
90 | }
91 | });
92 |
93 | // walk to find all variable declarations and remove the unused ones
94 | csstree.walk(ast, (node, item, list) => {
95 | if (
96 | node.type === 'Declaration' &&
97 | (node.property.startsWith('--v-') || node.property.startsWith('--c-'))
98 | ) {
99 | const variable = node.property;
100 |
101 | if (!usedCssVariables.includes(variable)) {
102 | list.remove(item);
103 | }
104 | }
105 | });
106 |
107 | const finalPath = replace ? file.path : file.path.replace('.css', '.min.css');
108 |
109 | await writeFile(finalPath, csstree.generate(ast, { mode: 'safe' }));
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/tools/tasks/restart-service.js:
--------------------------------------------------------------------------------
1 | import { exec } from 'child_process';
2 |
3 | import { Logger } from '../utils/logger.js';
4 | const logger = new Logger(restartService.name, 'info', 'brightRed');
5 |
6 | export async function restartService(serviceName) {
7 | return new Promise((resolve, reject) => {
8 | logger.info(`Restarting '${serviceName}' service...`);
9 |
10 | let command;
11 | let args;
12 |
13 | if (process.platform === 'win32') {
14 | command = 'cmd.exe';
15 | args = ['/c', 'net', 'stop', serviceName, '&&', 'net', 'start', serviceName];
16 | } else {
17 | command = 'sudo';
18 | args = ['systemctl', 'restart', serviceName];
19 | }
20 |
21 | exec(`${command} ${args.join(' ')}`, (error, stdout) => {
22 | if (error) {
23 | logger.error(`Failed to restart '${serviceName}' service: ${error}`);
24 | reject(error);
25 | } else {
26 | logger.info(`'${serviceName}' service restarted!`);
27 | resolve(stdout);
28 | }
29 | });
30 | });
31 | }
32 |
--------------------------------------------------------------------------------
/tools/tasks/scss.js:
--------------------------------------------------------------------------------
1 | import { mkdirSync, readdirSync, writeFileSync } from 'fs';
2 | import { join } from 'path';
3 | import { Logger } from '../utils/logger.js';
4 | import { compile } from 'sass';
5 |
6 | const logger = new Logger(buildScss.name, 'debug', 'pink');
7 | const themesSrc = 'themes/scss';
8 | const cssDistPath = '/public/assets/css';
9 |
10 | async function buildThemes(srcPath, distPath) {
11 | const themes = getScssFiles(srcPath, themesSrc);
12 |
13 | for (const theme of themes) {
14 | logger.debug(`Building ${theme.name} theme`);
15 |
16 | const result = compile(theme.path, {
17 | loadPaths: [join(srcPath, '../node_modules')],
18 | quietDeps: true,
19 | logger: {
20 | debug: logger.simpleDebug.bind(logger),
21 | info: logger.simpleInfo.bind(logger),
22 | warn: logger.simpleWarn.bind(logger),
23 | error: logger.simpleError.bind(logger),
24 | }
25 | });
26 |
27 | logger.debug(`Writing ${theme.name} theme to disk`);
28 |
29 | writeFileSync(
30 | join(distPath, cssDistPath, `theme-${theme.name}.css`),
31 | result.css
32 | );
33 | }
34 | }
35 |
36 | export async function buildScss(srcPath, distPath) {
37 | logger.info('SCSS build has started');
38 |
39 | mkdirSync(join(distPath, cssDistPath), { recursive: true });
40 |
41 | await buildThemes(srcPath, distPath);
42 | logger.info('SCSS build has finished');
43 | }
44 |
45 | function getScssFiles(srcHome, path) {
46 | try {
47 | return readdirSync(join(srcHome, path)).filter(
48 | (fn) => fn.endsWith('.scss') && !fn.startsWith('_')
49 | ).map((file) => ({
50 | name: file.replace('.scss', ''),
51 | path: join(srcHome, path, file),
52 | }))
53 | } catch (err) {
54 | return [];
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tools/tasks/templates.js:
--------------------------------------------------------------------------------
1 | import { join } from 'path';
2 | import { existsSync } from 'fs';
3 | import { copyFolderRecursiveSync } from '../utils/funcs.js';
4 | import { Logger } from '../utils/logger.js';
5 |
6 | const logger = new Logger(buildTemplates.name, 'info', 'blue');
7 | const tmplSrc = 'templates';
8 | const tmplDest = '/';
9 |
10 | export async function buildTemplates(srcHome, distHome) {
11 | logger.info('Templates build has started');
12 | const tmplSrcPath = join(srcHome, tmplSrc);
13 | const tmplDestPath = join(distHome, tmplDest);
14 |
15 | // if src/templates folder doesn't exist, just return
16 | if (!existsSync(tmplSrcPath)) {
17 | logger.info(`No templates found in ${tmplSrcPath}. Skipping templates build`);
18 | return;
19 | }
20 |
21 | // just copy the entire tmplSrcPath to tmplDestPath
22 | copyFolderRecursiveSync(tmplSrcPath, tmplDestPath);
23 | logger.info('Templates build has finished');
24 | }
25 |
--------------------------------------------------------------------------------
/tools/utils/funcs.js:
--------------------------------------------------------------------------------
1 | import {
2 | existsSync,
3 | lstatSync,
4 | mkdirSync,
5 | readFileSync,
6 | readdirSync,
7 | writeFileSync,
8 | } from 'fs';
9 | import { basename, join } from 'path';
10 |
11 | export function readFiles(path, extensions) {
12 | return readdirSync(path).filter((file) =>
13 | extensions.some((ext) => file.endsWith(ext))
14 | );
15 | }
16 |
17 | function copyFileSync(source, target) {
18 | var targetFile = target;
19 |
20 | // If target is a directory, a new file with the same name will be created
21 | if (existsSync(target)) {
22 | if (lstatSync(target).isDirectory()) {
23 | targetFile = join(target, basename(source));
24 | }
25 | }
26 |
27 | writeFileSync(targetFile, readFileSync(source));
28 | }
29 |
30 | export function copyFolderRecursiveSync(source, target) {
31 | var files = [];
32 |
33 | var targetFolder = join(target, basename(source));
34 | if (!existsSync(targetFolder)) {
35 | mkdirSync(targetFolder);
36 | }
37 |
38 | // Copy
39 | if (lstatSync(source).isDirectory()) {
40 | files = readdirSync(source);
41 | files.forEach(function (file) {
42 | var curSource = join(source, file);
43 | if (lstatSync(curSource).isDirectory()) {
44 | copyFolderRecursiveSync(curSource, targetFolder);
45 | } else {
46 | copyFileSync(curSource, targetFolder);
47 | }
48 | });
49 | }
50 | }
51 |
52 | // https://github.com/bjoerge/debounce-promise/blob/master/index.js
53 | export function debounce(fn, wait = 0, options = {}) {
54 | let lastCallAt;
55 | let deferred;
56 | let timer;
57 | let pendingArgs = [];
58 | return function debounced(...args) {
59 | const currentWait = getWait(wait);
60 | const currentTime = new Date().getTime();
61 |
62 | const isCold = !lastCallAt || currentTime - lastCallAt > currentWait;
63 |
64 | lastCallAt = currentTime;
65 |
66 | if (isCold && options.leading) {
67 | return options.accumulate
68 | ? Promise.resolve(fn.call(this, [args])).then((result) => result[0])
69 | : Promise.resolve(fn.call(this, ...args));
70 | }
71 |
72 | if (deferred) {
73 | clearTimeout(timer);
74 | } else {
75 | deferred = defer();
76 | }
77 |
78 | pendingArgs.push(args);
79 | timer = setTimeout(flush.bind(this), currentWait);
80 |
81 | if (options.accumulate) {
82 | const argsIndex = pendingArgs.length - 1;
83 | return deferred.promise.then((results) => results[argsIndex]);
84 | }
85 |
86 | return deferred.promise;
87 | };
88 |
89 | function flush() {
90 | const thisDeferred = deferred;
91 | clearTimeout(timer);
92 |
93 | Promise.resolve(
94 | options.accumulate
95 | ? fn.call(this, pendingArgs)
96 | : fn.apply(this, pendingArgs[pendingArgs.length - 1])
97 | ).then(thisDeferred.resolve, thisDeferred.reject);
98 |
99 | pendingArgs = [];
100 | deferred = null;
101 | }
102 | }
103 |
104 | function getWait(wait) {
105 | return typeof wait === 'function' ? wait() : wait;
106 | }
107 |
108 | function defer() {
109 | const deferred = {};
110 | deferred.promise = new Promise((resolve, reject) => {
111 | deferred.resolve = resolve;
112 | deferred.reject = reject;
113 | });
114 | return deferred;
115 | }
116 |
117 | export async function sequence(tasks) {
118 | const results = [];
119 | for (const task of tasks) {
120 | results.push(await task());
121 | }
122 | return results;
123 | }
124 |
125 |
126 | async function tasksRunner(tasks, abort) {
127 | let result = null;
128 | for (const task of tasks) {
129 | if (abort.signal.aborted) {
130 | break;
131 | }
132 |
133 | result = await task(result);
134 | }
135 | }
136 |
137 | // each task should return a promise
138 | // each task takes the result of the previous task as an argument
139 | // the output of the last task is the output of the sequence
140 | export async function sequenceStream(tasks) {
141 | const abort = new AbortController();
142 |
143 | abort.signal.addEventListener('abort', () => {
144 | console.log('sequenceStream aborted');
145 | });
146 |
147 | return [
148 | tasksRunner(tasks, abort),
149 | abort,
150 | ]
151 | }
152 |
153 | export const getArg = (flag, def) => {
154 | const args = process.argv.slice(2);
155 | const flagIndex = args.findIndex(arg => arg === flag);
156 |
157 | if (flagIndex !== -1 && flagIndex + 1 < args.length) {
158 | return args[flagIndex + 1];
159 | }
160 |
161 | return def || null;
162 | };
--------------------------------------------------------------------------------
/tools/utils/logger.js:
--------------------------------------------------------------------------------
1 | const LOG_LEVEL_MAP = {
2 | debug: 0,
3 | info: 1,
4 | warn: 2,
5 | error: 3,
6 | };
7 |
8 | const ANSI_COLORS = {
9 | reset: '\x1b[0m',
10 | black: '\x1b[30m',
11 | red: '\x1b[31m',
12 | green: '\x1b[32m',
13 | yellow: '\x1b[33m',
14 | blue: '\x1b[34m',
15 | magenta: '\x1b[35m',
16 | cyan: '\x1b[36m',
17 | white: '\x1b[37m',
18 | gray: '\x1b[90m',
19 | brightRed: '\x1b[91m',
20 | brightGreen: '\x1b[92m',
21 | brightYellow: '\x1b[93m',
22 | brightBlue: '\x1b[94m',
23 | brightMagenta: '\x1b[95m',
24 | brightCyan: '\x1b[96m',
25 | pink: '\x1b[38;2;255;182;193m'
26 | };
27 |
28 | export class Logger {
29 | /**
30 | * @param {string} ctx
31 | * @param {'debug'|'info'|'warn'|'error'} log_level - default: 'debug'
32 | * @param {'red'|'green'|'yellow'|'blue'|'magenta'|'cyan'|'white'|'gray'|'brightRed'|
33 | * 'brightGreen'|'brightYellow'|'brightBlue'|'brightMagenta'|'brightCyan'|'pink'} color - default: 'magenta'
34 | */
35 | constructor(ctx, log_level, color = 'magenta') {
36 | this.ctx = ctx;
37 | this.log_level = LOG_LEVEL_MAP[log_level || 'debug'];
38 | this.color = ANSI_COLORS[color] || ANSI_COLORS.reset;
39 |
40 | if (this.log_level === undefined) {
41 | throw new Error(`Invalid log level: ${log_level}`);
42 | }
43 | }
44 |
45 | debug(...args) {
46 | if (!this.#canLog('debug')) return;
47 | this.log('DEBUG', false, ...args);
48 | }
49 |
50 | info(...args) {
51 | if (!this.#canLog('info')) return;
52 | this.log('INFO', false, ...args);
53 | }
54 |
55 | warn(...args) {
56 | if (!this.#canLog('warn')) return;
57 | this.log('WARN', false, ...args);
58 |
59 | }
60 |
61 | error(...args) {
62 | if (!this.#canLog('error')) return;
63 | this.log('ERROR', false, ...args);
64 | }
65 |
66 | log(level, simple, ...args) {
67 | if (simple) {
68 | args = [args[0]];
69 | }
70 |
71 | if (level === 'ERROR') {
72 | console.error(
73 | `${this.color}[${level}] [${this.ctx}]${ANSI_COLORS.reset}`,
74 | ...args
75 | );
76 | } else {
77 | console.log('🍵', `${this.color}[${this.ctx}]${ANSI_COLORS.reset}`, ...args);
78 | }
79 | }
80 |
81 | simpleDebug(...args) {
82 | if (!this.#canLog('debug')) return;
83 | this.log('DEBUG', true, ...args);
84 | }
85 |
86 | simpleInfo(...args) {
87 | if (!this.#canLog('info')) return;
88 | this.log('INFO', true, ...args);
89 | }
90 |
91 | simpleWarn(...args) {
92 | if (!this.#canLog('warn')) return;
93 | this.log('WARN', true, ...args);
94 |
95 | }
96 |
97 | simpleError(...args) {
98 | if (!this.#canLog('error')) return;
99 | this.log('ERROR', true, ...args);
100 | }
101 |
102 | #canLog(level) {
103 | return this.log_level <= LOG_LEVEL_MAP[level];
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/tools/utils/task-debouncer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * #### TaskDebouncer
3 | *
4 | * Executes a task after a certain delay, but cancels the execution if
5 | * a new task is sent before the delay expires. Also, if a task is
6 | * already being executed, the new task is queued and executed after
7 | * the current one finishes. It will only execute the task that was
8 | * sent last.
9 | */
10 | export class TaskDebouncer {
11 | constructor(debounceDelay) {
12 | this.debounceDelay = debounceDelay;
13 | this.queued = undefined;
14 | this.isProcessing = false;
15 | this.timerId = null;
16 | }
17 |
18 | #clearQueue() {
19 | this.queued = undefined;
20 | }
21 |
22 | #enqueue(executor, args) {
23 | this.queued = { executor, args };
24 | this.#processQueue();
25 | }
26 |
27 | #setProcessing(value) {
28 | this.isProcessing = value;
29 | }
30 |
31 | async #processQueue() {
32 | if (this.isProcessing || !this.queued) {
33 | return;
34 | }
35 | const { executor, args } = this.queued;
36 | this.#clearQueue();
37 |
38 | // execute the task
39 | this.#setProcessing(true);
40 | await executor(...args);
41 | this.#setProcessing(false);
42 |
43 | // continue with the next task
44 | this.#continue();
45 | }
46 |
47 | #continue() {
48 | if (this.queued) {
49 | this.#processQueue();
50 | }
51 | }
52 |
53 | /**
54 | * Adds a task to the queue. If a task is already being executed,
55 | * the new task is queued and executed after the current one finishes.
56 | * It will only execute the task if no other task is sent before the
57 | * delay expires or before the current task finishes.
58 | *
59 | * IOW, it will only execute the task that was sent last.
60 | */
61 | add(executor, ...args) {
62 | clearTimeout(this.timerId);
63 | this.timerId = setTimeout(() => {
64 | this.#enqueue(executor, args);
65 | }, this.debounceDelay);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/🍵 lugit-theme.code-workspace:
--------------------------------------------------------------------------------
1 | {
2 | "folders": [
3 | {
4 | "path": "."
5 | }
6 | ],
7 | "settings": {
8 | "files.exclude": {
9 | "**/.git": false,
10 | "**/.svn": false,
11 | "**/.hg": false,
12 | "**/CVS": false,
13 | "**/.DS_Store": false,
14 | "**/Thumbs.db": true,
15 | "**/.project": false,
16 | "**/.classpath": false,
17 | ".vscode": false,
18 | ".settings": false,
19 | ".idea/": false,
20 | ".out/": false,
21 | "out/": false,
22 | "*.iml": false,
23 | "*.iws": false,
24 | "*.ipr": false,
25 | "*.ids": false,
26 | "*.orig": false,
27 | "build/": false,
28 | "bin/": false,
29 | ".gradle": false,
30 | "**/.settings": true,
31 | "**/.factorypath": true
32 | }
33 |
34 | }
35 | }
--------------------------------------------------------------------------------