Compare commits

..

4 Commits

Author SHA1 Message Date
MangoPig
a75293fce4 Feat: Frontend hardening 2026-06-14 15:22:19 +01:00
MangoPig
883e8a8bcc Feat: Frontend app shell 2026-06-14 15:21:02 +01:00
MangoPig
282e6b604d Feat: Documentations
Fix
2026-06-14 03:07:51 +01:00
MangoPig
fdd6fc8376 Feat: Icons and Typography 2026-06-13 12:39:11 +01:00
29 changed files with 1400 additions and 108 deletions

161
Documentation/CONTRIBUTING Normal file
View File

@@ -0,0 +1,161 @@
# Contributing
Thanks for contributing to Moku Work.
This project is still in an early scaffold stage, so the goal is to keep changes easy to understand, easy to review, and easy to undo.
## Getting Started
### Project structure
- `Frontend/` — SolidStart frontend workspace
- `Backend/` — backend placeholder
- `Proxy/` — proxy placeholder
- `Docker/` — local Docker Compose files
- `Env/` — local environment files
- `Commands/` — Just command modules and entrypoints
### Prerequisites
Before working on the project, make sure you have:
- `just`
- Docker with `docker compose`
- Docker Buildx
- Node.js `>=22`
- `pnpm@9`
### Local development
List available commands:
```bash
just --list --list-submodules
```
Main local development flow:
```bash
just local dev
```
This command builds the frontend development image and starts the local development stack.
### Local environment
Local development uses:
```bash
Env/.env.local
```
If local environment values are missing, create or update that file before starting the stack.
## Commit Naming Convention
Use short, clear commit messages that describe one logical change.
Preferred format:
```text
Type: Short description
```
Examples:
```text
Feat: Add login page scaffold
Fix: Resolve mobile sidebar overflow
Refactor: Split theme helpers from app entry
Docs: Add local development notes
Chore: Update frontend dependencies
```
Recommended commit types:
- `Feat:` — new feature or visible behavior
- `Fix:` — bug fix
- `Refactor:` — internal code change without behavior change
- `Docs:` — documentation only
- `Chore:` — maintenance, tooling, dependency, or housekeeping work
- `Style:` — formatting or styling-only cleanup
- `Test:` — tests added or updated
### Commit message rules
- Keep the subject line short and specific
- Start with a capitalized type
- Describe what changed, not the entire session
- One commit should usually be explainable in one sentence
Good:
```text
Feat: Add base dashboard layout
Fix: Correct broken theme toggle import
Docs: Document branch naming workflow
```
Bad:
```text
stuff
more updates
big rewrite
misc fixes
```
## Branch Naming Convention
Use branch names that make the purpose of the work obvious at a glance.
Preferred format:
```text
type/short-description
```
Examples:
```text
feature/login-page
fix/mobile-sidebar-overflow
refactor/theme-helpers
docs/contributing-update
chore/frontend-dependency-updates
rewrite/v2-foundation
```
Recommended branch types:
- `feature/` — new feature work
- `fix/` — bug fixes
- `refactor/` — structural code changes without intended behavior changes
- `docs/` — documentation work
- `chore/` — maintenance or tooling updates
- `rewrite/` — large rebuilds or architecture overhauls
### Branch naming rules
- Use lowercase words
- Separate words with hyphens
- Keep the name short but specific
- Name the branch after the outcome, not the time spent on it
- For larger efforts, prefer one clear branch name over vague labels like `misc` or `updates`
Good:
```text
feature/auth-shell
fix/docker-local-env-loading
rewrite/new-routing-foundation
```
Bad:
```text
new-stuff
work-branch
test
misc-updates
```

70
Documentation/TODO.md Normal file
View File

@@ -0,0 +1,70 @@
# ToDo
## Version 1.0.0 Roadmap
### Version 0.1.0
**Goal:** Barebone frontend with a real backend core.
#### Architecture
- [ ] Project-Structure
- [ ] Stack-Decisions
- [ ] Proxy
- [ ] Dev-and-Prod-Builds
#### Backend
- [ ] Auth
- [ ] Session-Flow
- [ ] Login-Logout-Foundation
- [ ] Authentication
- [ ] User
- [ ] Base-Model
- [ ] Base-Workspace
- [ ] Folders-and-Subfolders
- [ ] Boards
- [ ] Dashboard
- [ ] Organization
- [ ] Base-Model
- [ ] Access-Rules-and-Membership
- [ ] Workspace
- [ ] Folders-and-Subfolders
- [ ] API
#### Frontend
- [x] Foundation
- [x] Typography
- [x] Icons
- [ ] App Shell
- [ ] Primitives
- [ ] Button
- [ ] IconButton
- [ ] Input
- [ ] Surface
- [ ] Nav-Bar
- [ ] Workspace-Switching
- [ ] Workspace-Home
### Version 0.2.0
**Goal:** First real work surface.
- [ ] Table
- [ ] CVA
- [ ] Storyboard
### Version 0.3.0
**Goal:** Documents and system hardening.
- [ ] Document
- [ ] Accessibility-Rules
- [ ] Motion-Foundation
### Version 0.4.0
- [ ] Gantt-Board
- [ ] Calendar
- [ ] Timeline

View File

@@ -11,18 +11,21 @@
"scripts": {
"dev": "vite dev",
"build": "vite build",
"start": "vite start",
"typecheck": "tsc --noEmit",
"start": "vite preview",
"preview": "vite preview"
},
"dependencies": {
"@solidjs/start": "2.0.0-alpha.2",
"@solidjs/vite-plugin-nitro-2": "^0.1.0",
"lucide-solid": "^0.542.0",
"postcss": "^8.5.15",
"sass": "^1.101.0",
"solid-js": "^1.9.5",
"vite": "^7.0.0"
},
"devDependencies": {
"@types/node": "^25.9.3",
"autoprefixer": "^10.5.0",
"cssnano": "^8.0.2",
"postcss-preset-env": "^11.3.0",

View File

@@ -10,10 +10,13 @@ importers:
dependencies:
'@solidjs/start':
specifier: 2.0.0-alpha.2
version: 2.0.0-alpha.2(crossws@0.4.4(srvx@0.11.8))(vite@7.3.1(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0))
version: 2.0.0-alpha.2(crossws@0.4.4(srvx@0.11.8))(vite@7.3.1(@types/node@25.9.3)(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0))
'@solidjs/vite-plugin-nitro-2':
specifier: ^0.1.0
version: 0.1.0(vite@7.3.1(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0))
version: 0.1.0(vite@7.3.1(@types/node@25.9.3)(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0))
lucide-solid:
specifier: ^0.542.0
version: 0.542.0(solid-js@1.9.11)
postcss:
specifier: ^8.5.15
version: 8.5.15
@@ -25,8 +28,11 @@ importers:
version: 1.9.11
vite:
specifier: ^7.0.0
version: 7.3.1(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0)
version: 7.3.1(@types/node@25.9.3)(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0)
devDependencies:
'@types/node':
specifier: ^25.9.3
version: 25.9.3
autoprefixer:
specifier: ^10.5.0
version: 10.5.0(postcss@8.5.15)
@@ -1259,6 +1265,9 @@ packages:
'@types/micromatch@4.0.10':
resolution: {integrity: sha512-5jOhFDElqr4DKTrTEbnW8DZ4Hz5LRUEmyrGpCMrD/NphYv3nUnaF08xmSLx1rGGnyEs/kFnhiw6dCgcDqMr5PQ==}
'@types/node@25.9.3':
resolution: {integrity: sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==}
'@types/resolve@1.20.2':
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
@@ -2151,6 +2160,11 @@ packages:
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
lucide-solid@0.542.0:
resolution: {integrity: sha512-cMy0fZu9TEEm1IIP3BIXBJd07xJGeAYNebIE1Zti+7cnv1hJcHoNZx4+D2tHZIYKILoSB/Rhs5M0vdKzQTU33g==}
peerDependencies:
solid-js: ^1.4.7
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
@@ -3209,6 +3223,9 @@ packages:
unctx@2.5.0:
resolution: {integrity: sha512-p+Rz9x0R7X+CYDkT+Xg8/GhpcShTlU8n+cf9OtOEf7zEQsNcCZO1dPKNRDqvUTaq+P32PMMkxWHwfrxkqfqAYg==}
undici-types@7.24.6:
resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==}
unenv@2.0.0-rc.24:
resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==}
@@ -4468,13 +4485,13 @@ snapshots:
dependencies:
solid-js: 1.9.11
'@solidjs/start@2.0.0-alpha.2(crossws@0.4.4(srvx@0.11.8))(vite@7.3.1(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0))':
'@solidjs/start@2.0.0-alpha.2(crossws@0.4.4(srvx@0.11.8))(vite@7.3.1(@types/node@25.9.3)(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0))':
dependencies:
'@babel/core': 7.29.0
'@babel/traverse': 7.29.0
'@babel/types': 7.29.0
'@solidjs/meta': 0.29.4(solid-js@1.9.11)
'@tanstack/server-functions-plugin': 1.134.5(vite@7.3.1(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0))
'@tanstack/server-functions-plugin': 1.134.5(vite@7.3.1(@types/node@25.9.3)(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0))
'@types/babel__traverse': 7.28.0
'@types/micromatch': 4.0.10
cookie-es: 2.0.0
@@ -4496,17 +4513,17 @@ snapshots:
source-map-js: 1.2.1
srvx: 0.9.8
terracotta: 1.1.0(solid-js@1.9.11)
vite: 7.3.1(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0)
vite-plugin-solid: 2.11.10(solid-js@1.9.11)(vite@7.3.1(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0))
vite: 7.3.1(@types/node@25.9.3)(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0)
vite-plugin-solid: 2.11.10(solid-js@1.9.11)(vite@7.3.1(@types/node@25.9.3)(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0))
transitivePeerDependencies:
- '@testing-library/jest-dom'
- crossws
- supports-color
'@solidjs/vite-plugin-nitro-2@0.1.0(vite@7.3.1(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0))':
'@solidjs/vite-plugin-nitro-2@0.1.0(vite@7.3.1(@types/node@25.9.3)(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0))':
dependencies:
nitropack: 2.13.1
vite: 7.3.1(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0)
vite: 7.3.1(@types/node@25.9.3)(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0)
transitivePeerDependencies:
- '@azure/app-configuration'
- '@azure/cosmos'
@@ -4541,7 +4558,7 @@ snapshots:
'@speed-highlight/core@1.2.14': {}
'@tanstack/directive-functions-plugin@1.134.5(vite@7.3.1(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0))':
'@tanstack/directive-functions-plugin@1.134.5(vite@7.3.1(@types/node@25.9.3)(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0))':
dependencies:
'@babel/code-frame': 7.27.1
'@babel/core': 7.29.0
@@ -4551,7 +4568,7 @@ snapshots:
babel-dead-code-elimination: 1.0.12
pathe: 2.0.3
tiny-invariant: 1.3.3
vite: 7.3.1(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0)
vite: 7.3.1(@types/node@25.9.3)(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0)
transitivePeerDependencies:
- supports-color
@@ -4568,7 +4585,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@tanstack/server-functions-plugin@1.134.5(vite@7.3.1(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0))':
'@tanstack/server-functions-plugin@1.134.5(vite@7.3.1(@types/node@25.9.3)(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0))':
dependencies:
'@babel/code-frame': 7.27.1
'@babel/core': 7.29.0
@@ -4577,7 +4594,7 @@ snapshots:
'@babel/template': 7.28.6
'@babel/traverse': 7.29.0
'@babel/types': 7.29.0
'@tanstack/directive-functions-plugin': 1.134.5(vite@7.3.1(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0))
'@tanstack/directive-functions-plugin': 1.134.5(vite@7.3.1(@types/node@25.9.3)(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0))
babel-dead-code-elimination: 1.0.12
tiny-invariant: 1.3.3
transitivePeerDependencies:
@@ -4621,6 +4638,10 @@ snapshots:
dependencies:
'@types/braces': 3.0.5
'@types/node@25.9.3':
dependencies:
undici-types: 7.24.6
'@types/resolve@1.20.2': {}
'@types/unist@3.0.3': {}
@@ -5522,6 +5543,10 @@ snapshots:
dependencies:
yallist: 3.1.1
lucide-solid@0.542.0(solid-js@1.9.11):
dependencies:
solid-js: 1.9.11
magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -6741,6 +6766,8 @@ snapshots:
magic-string: 0.30.21
unplugin: 2.3.11
undici-types@7.24.6: {}
unenv@2.0.0-rc.24:
dependencies:
pathe: 2.0.3
@@ -6864,7 +6891,7 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.3
vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@7.3.1(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0)):
vite-plugin-solid@2.11.10(solid-js@1.9.11)(vite@7.3.1(@types/node@25.9.3)(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0)):
dependencies:
'@babel/core': 7.29.0
'@types/babel__core': 7.20.5
@@ -6872,12 +6899,12 @@ snapshots:
merge-anything: 5.1.7
solid-js: 1.9.11
solid-refresh: 0.6.3(solid-js@1.9.11)
vite: 7.3.1(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0)
vitefu: 1.1.2(vite@7.3.1(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0))
vite: 7.3.1(@types/node@25.9.3)(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0)
vitefu: 1.1.2(vite@7.3.1(@types/node@25.9.3)(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0))
transitivePeerDependencies:
- supports-color
vite@7.3.1(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0):
vite@7.3.1(@types/node@25.9.3)(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0):
dependencies:
esbuild: 0.27.3
fdir: 6.5.0(picomatch@4.0.3)
@@ -6886,15 +6913,16 @@ snapshots:
rollup: 4.59.0
tinyglobby: 0.2.15
optionalDependencies:
'@types/node': 25.9.3
fsevents: 2.3.3
jiti: 2.6.1
sass: 1.101.0
sass-embedded: 1.100.0
terser: 5.46.0
vitefu@1.1.2(vite@7.3.1(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0)):
vitefu@1.1.2(vite@7.3.1(@types/node@25.9.3)(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0)):
optionalDependencies:
vite: 7.3.1(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0)
vite: 7.3.1(@types/node@25.9.3)(jiti@2.6.1)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.46.0)
webidl-conversions@3.0.1: {}

View File

@@ -1,7 +1,11 @@
// Path: Frontend/src/app.tsx
import type { JSX } from "solid-js";
import { AppShell } from "./components/shell/AppShell/AppShell";
import "./styles/main.scss";
export default function App() {
return <main />;
}
const App = (): JSX.Element => {
return <AppShell />;
};
export default App;

View File

@@ -0,0 +1,135 @@
.shell {
height: 100dvh;
min-height: 100dvh;
display: grid;
grid-template-rows: auto minmax(0, 1fr);
background: var(--color-canvas);
color: var(--color-text);
}
.body {
--rail-width: 4.75rem;
--sidebar-width: 16.75rem;
--shell-top-left-radius: calc(var(--radius-xl) + var(--space-1));
--shell-frame-border: color-mix(in srgb, var(--color-border-strong) 44%, transparent);
--shell-divider-border: color-mix(in srgb, var(--color-border-strong) 34%, transparent);
--sidebar-panel-surface: color-mix(in srgb, var(--color-surface-muted) 92%, transparent);
--workspace-panel-surface: color-mix(in srgb, var(--color-canvas) 94%, var(--color-surface));
min-height: 0;
display: grid;
grid-template-columns: var(--rail-width) minmax(0, 1fr);
overflow: hidden;
background: var(--color-surface);
}
.railColumn {
min-height: 0;
display: flex;
position: relative;
z-index: 1;
background: var(--color-surface);
}
.workspaceRegion {
position: relative;
min-width: 0;
min-height: 0;
display: grid;
grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
overflow: visible;
z-index: 1;
isolation: isolate;
border-top-left-radius: var(--shell-top-left-radius);
border-top-right-radius: 0;
}
.workspaceRegion::before {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(
to right,
var(--sidebar-panel-surface) 0,
var(--sidebar-panel-surface) calc(var(--sidebar-width) - 0.5px),
var(--workspace-panel-surface) calc(var(--sidebar-width) - 0.5px),
var(--workspace-panel-surface) 100%
);
border: 1px solid var(--shell-frame-border);
border-top: 0;
border-top-left-radius: var(--shell-top-left-radius);
border-top-right-radius: 0;
box-shadow: inset 0 1px 0 color-mix(in srgb, white 3%, transparent);
pointer-events: none;
z-index: 0;
}
.sidebarColumn {
position: relative;
min-width: 0;
min-height: 0;
display: grid;
grid-template-rows: minmax(0, 1fr);
overflow: visible;
z-index: 1;
background: var(--sidebar-panel-surface);
border-top: 1px solid var(--shell-frame-border);
border-left: 1px solid var(--shell-frame-border);
border-top-left-radius: var(--shell-top-left-radius);
}
.workspaceMain {
min-width: 0;
min-height: 0;
position: relative;
overflow: hidden;
z-index: 1;
border-top: 1px solid var(--shell-frame-border);
border-left: 1px solid var(--shell-divider-border);
background: var(--workspace-panel-surface);
border-top-right-radius: 0;
}
.sidebarDock {
position: absolute;
right: var(--space-1);
bottom: var(--space-3);
left: calc(var(--space-1) - (var(--rail-width) * 0.9));
z-index: calc(var(--z-modal) + 1);
pointer-events: none;
> * {
pointer-events: auto;
}
}
@include respond-up(mobile) {
.body {
--rail-width: 5rem;
--sidebar-width: 17.25rem;
}
}
@include respond-down(tablet) {
.body {
--rail-width: 4.5rem;
--sidebar-width: 13.25rem;
}
}
@include respond-down(mobile) {
.body {
grid-template-columns: 4.5rem minmax(0, 1fr);
--rail-width: 4.5rem;
}
.railColumn {
position: sticky;
top: 0;
}
.workspaceRegion,
.sidebarDock {
display: none;
}
}

View File

@@ -0,0 +1,50 @@
// Path: Frontend/src/components/shell/AppShell/AppShell.tsx
import { createSignal, onMount, type JSX } from "solid-js";
import { getDocumentTheme, setTheme, type Theme } from "../../../helpers/theme";
import { WorkspaceHome } from "../../workspace-home/WorkspaceHome/WorkspaceHome";
import { LeftRail } from "../LeftRail/LeftRail";
import { ProfileDock } from "../ProfileDock/ProfileDock";
import { TopBar } from "../TopBar/TopBar";
import { WorkspaceSidebar } from "../WorkspaceSidebar/WorkspaceSidebar";
import styles from "./AppShell.module.scss";
export const AppShell = (): JSX.Element => {
const [themeState, setThemeState] = createSignal<Theme>("light");
onMount((): void => {
setThemeState(getDocumentTheme());
});
const toggleTheme = (): void => {
const next: Theme = themeState() === "dark" ? "light" : "dark";
setTheme(next);
setThemeState(next);
};
return (
<div class={styles.shell}>
<TopBar theme={themeState()} onToggleTheme={toggleTheme} />
<div class={styles.body}>
<div class={styles.railColumn}>
<LeftRail />
</div>
<div class={styles.workspaceRegion}>
<div class={styles.sidebarColumn}>
<WorkspaceSidebar />
<div class={styles.sidebarDock}>
<ProfileDock />
</div>
</div>
<div class={styles.workspaceMain}>
<WorkspaceHome />
</div>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,82 @@
.rail {
--rail-workspace-size: var(--control-size-lg);
--rail-action-size: var(--control-size-md);
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-3);
padding: var(--space-3) var(--space-2);
overflow: hidden;
}
.topCluster,
.bottomCluster {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-2);
}
.items {
width: 100%;
min-height: 0;
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-2);
overflow-y: auto;
overscroll-behavior: contain;
padding-block: var(--space-1);
}
.logo {
width: var(--rail-workspace-size);
height: var(--rail-workspace-size);
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-lg);
background: var(--color-accent);
color: var(--color-accent-contrast);
font-weight: 700;
letter-spacing: -0.02em;
}
.workspaceButton {
width: var(--rail-workspace-size);
height: var(--rail-workspace-size);
display: inline-flex;
align-items: center;
justify-content: center;
@include interactive-frame(var(--color-surface-muted), var(--color-border), var(--color-text-muted), var(--radius-lg));
@include text-label;
@include interactive-frame-hover();
}
.workspaceButtonActive {
background: var(--color-accent);
border-color: transparent;
color: var(--color-accent-contrast);
box-shadow: var(--shadow-soft);
}
.addButton {
width: var(--rail-action-size);
height: var(--rail-action-size);
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px dashed var(--color-border-strong);
border-radius: var(--radius-pill);
background: transparent;
color: var(--color-text-muted);
&:hover {
background: var(--color-surface-hover);
color: var(--color-text);
}
}

View File

@@ -0,0 +1,42 @@
// Path: Frontend/src/components/shell/LeftRail/LeftRail.tsx
import { For, type JSX } from "solid-js";
import { Plus } from "../../../lib/icons";
import { railItems } from "../data/shell.data";
import styles from "./LeftRail.module.scss";
export const LeftRail = (): JSX.Element => {
return (
<aside class={styles.rail} aria-label="Workspace rail">
<div class={styles.topCluster}>
<div class={styles.logo} aria-hidden="true">
M
</div>
</div>
<div class={styles.items}>
<For each={railItems}>
{(item): JSX.Element => (
<button
type="button"
classList={{
[styles.workspaceButton]: true,
[styles.workspaceButtonActive]: !!item.active,
}}
title={item.label}
aria-label={item.label}
>
{item.abbreviation}
</button>
)}
</For>
</div>
<div class={styles.bottomCluster}>
<button type="button" class={styles.addButton} aria-label="Create workspace" title="Create workspace">
<Plus size={16} strokeWidth={2} />
</button>
</div>
</aside>
);
};

View File

@@ -0,0 +1,85 @@
.panel {
--profile-dock-avatar-size: var(--control-size-md);
--profile-dock-action-min-height: var(--space-8);
--profile-dock-border: color-mix(in srgb, var(--color-border-strong) 75%, transparent);
--profile-dock-surface: color-mix(in srgb, var(--color-surface) 94%, transparent);
--profile-dock-status-ring: 0 0 0 3px color-mix(in srgb, var(--color-success) 18%, transparent);
position: relative;
z-index: 1;
width: 100%;
display: grid;
gap: var(--space-2);
padding: var(--space-3) var(--space-3) var(--space-2);
border: 1px solid var(--profile-dock-border);
border-radius: calc(var(--radius-xl) + var(--space-1));
background: var(--profile-dock-surface);
box-shadow:
0 20px 48px color-mix(in srgb, black 16%, transparent),
var(--shadow-strong);
backdrop-filter: blur(var(--blur-overlay));
}
.identity {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
gap: var(--space-3);
align-items: center;
}
.avatar {
width: var(--profile-dock-avatar-size);
height: var(--profile-dock-avatar-size);
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: var(--color-accent-soft);
color: var(--color-accent-strong);
@include text-label;
}
.copy {
min-width: 0;
display: grid;
gap: 0.15rem;
}
.name {
@include text-label;
}
.status {
@include text-caption;
display: inline-flex;
align-items: center;
gap: var(--space-2);
color: var(--color-text-muted);
}
.statusDot {
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
background: var(--color-success);
box-shadow: var(--profile-dock-status-ring);
}
.actions {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: var(--space-2);
}
.action {
min-height: var(--profile-dock-action-min-height);
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-1);
@include interactive-frame(var(--color-surface-muted));
@include interactive-frame-hover();
}
.actionLabel {
@include text-caption;
}

View File

@@ -0,0 +1,35 @@
// Path: Frontend/src/components/shell/ProfileDock/ProfileDock.tsx
import type { JSX } from "solid-js";
import { Settings, User } from "../../../lib/icons";
import styles from "./ProfileDock.module.scss";
export const ProfileDock = (): JSX.Element => {
return (
<section class={styles.panel} aria-label="Profile dock">
<div class={styles.identity}>
<div class={styles.avatar} aria-hidden="true">
R
</div>
<div class={styles.copy}>
<span class={styles.name}>Ronald</span>
<span class={styles.status}>
<span class={styles.statusDot} aria-hidden="true" />
Online in Moku
</span>
</div>
</div>
<div class={styles.actions}>
<button type="button" class={styles.action}>
<User size={16} strokeWidth={2} />
<span class={styles.actionLabel}>Account</span>
</button>
<button type="button" class={styles.action}>
<Settings size={16} strokeWidth={2} />
<span class={styles.actionLabel}>Prefs</span>
</button>
</div>
</section>
);
};

View File

@@ -0,0 +1,85 @@
.topBar {
--topbar-control-size: var(--control-size-md);
min-height: 4rem;
display: grid;
grid-template-columns: minmax(0, 1fr) auto auto;
align-items: center;
gap: var(--space-3);
padding: var(--space-3) var(--space-4) var(--space-3);
background: var(--color-surface);
}
.identity {
min-width: 0;
display: grid;
gap: 0;
}
.eyebrow {
@include text-caption;
color: var(--color-text-muted);
text-transform: uppercase;
letter-spacing: 0.08em;
}
.title {
@include text-title;
display: flex;
align-items: center;
gap: var(--space-2);
strong {
font: inherit;
font-weight: var(--font-weight-title);
}
}
.context {
color: var(--color-text-muted);
}
.actions {
display: flex;
align-items: center;
gap: var(--space-1);
}
.actionButton,
.themeButton {
height: var(--topbar-control-size);
display: inline-flex;
align-items: center;
justify-content: center;
@include interactive-frame();
}
.actionButton {
width: var(--topbar-control-size);
}
.themeButton {
width: auto;
padding-inline: var(--space-2);
gap: var(--space-1);
color: var(--color-text);
}
.actionButton,
.themeButton {
@include interactive-frame-hover();
}
.themeLabel {
@include text-label;
}
@include respond-down(mobile) {
.topBar {
grid-template-columns: minmax(0, 1fr) auto;
padding: var(--space-2) var(--space-3) var(--space-3);
}
.actions {
display: none;
}
}

View File

@@ -0,0 +1,45 @@
// Path: Frontend/src/components/shell/TopBar/TopBar.tsx
import { For, type JSX } from "solid-js";
import type { Theme } from "../../../helpers/theme";
import { ChevronDown } from "../../../lib/icons";
import { topBarActions } from "../data/shell.data";
import styles from "./TopBar.module.scss";
type TopBarProps = {
theme: Theme;
onToggleTheme: VoidFunction;
};
export const TopBar = (props: TopBarProps): JSX.Element => {
return (
<header class={styles.topBar}>
<div class={styles.identity}>
<span class={styles.eyebrow}>Moku Work</span>
<div class={styles.title}>
<strong>Workspace Shell</strong>
<span class={styles.context}>Moku / Product</span>
<ChevronDown size={16} strokeWidth={2} />
</div>
</div>
<button class={styles.themeButton} type="button" onClick={props.onToggleTheme}>
<span class={styles.themeLabel}>{props.theme === "dark" ? "Dark" : "Light"}</span>
</button>
<div class={styles.actions}>
<For each={topBarActions}>
{(item): JSX.Element => {
const Icon = item.icon;
return (
<button class={styles.actionButton} type="button" aria-label={item.label} title={item.label}>
<Icon size={18} strokeWidth={2} />
</button>
);
}}
</For>
</div>
</header>
);
};

View File

@@ -0,0 +1,103 @@
.sidebar {
--sidebar-nav-item-min-height: var(--control-size-lg);
--sidebar-dock-clearance: 8rem;
min-width: 0;
min-height: 0;
display: grid;
grid-template-rows: auto auto minmax(0, 1fr);
gap: var(--space-4);
padding: var(--space-4);
overflow: hidden;
}
.header {
display: grid;
gap: 0.2rem;
}
.eyebrow {
@include text-caption;
color: var(--color-text-muted);
text-transform: uppercase;
letter-spacing: 0.08em;
}
.title {
@include text-title;
}
.meta {
@include text-caption;
color: var(--color-text-muted);
max-width: 28ch;
}
.section {
display: grid;
grid-template-rows: auto minmax(0, 1fr);
gap: var(--space-2);
min-height: 0;
}
.navScroller {
min-height: 0;
overflow-y: auto;
overscroll-behavior: contain;
padding-right: var(--space-1);
padding-bottom: calc(var(--space-4) + var(--sidebar-dock-clearance));
margin-right: calc(var(--space-1) * -1);
}
.sectionLabel {
@include text-label;
color: var(--color-text-muted);
}
.navList {
list-style: none;
display: grid;
gap: var(--space-1);
padding: 0;
}
.navItem {
width: 100%;
min-width: 0;
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
align-items: center;
gap: var(--space-2);
min-height: var(--sidebar-nav-item-min-height);
padding: var(--space-2) var(--space-3);
@include interactive-frame(transparent, transparent, var(--color-text-muted), var(--radius-lg));
text-align: left;
@include interactive-frame-hover(var(--color-surface-hover), transparent, var(--color-text));
}
.navItemActive {
border-color: var(--color-border);
background: var(--color-surface);
color: var(--color-text);
box-shadow: var(--shadow-soft);
}
.icon {
color: inherit;
opacity: 0.85;
}
.label {
@include text-label;
min-width: 0;
}
.itemMeta {
@include text-caption;
color: var(--color-text-muted);
}
@include respond-down(mobile) {
.sidebar {
display: none;
}
}

View File

@@ -0,0 +1,48 @@
// Path: Frontend/src/components/shell/WorkspaceSidebar/WorkspaceSidebar.tsx
import { For, Show, type JSX } from "solid-js";
import { workspaceSidebarItems } from "../data/shell.data";
import styles from "./WorkspaceSidebar.module.scss";
export const WorkspaceSidebar = (): JSX.Element => {
return (
<aside class={styles.sidebar} aria-label="Workspace navigation">
<div class={styles.header}>
<span class={styles.eyebrow}>Workspace</span>
<h2 class={styles.title}>Product Operations</h2>
<p class={styles.meta}>A barebone shell for Mokus first real workspace layout.</p>
</div>
<div class={styles.section}>
<span class={styles.sectionLabel}>Navigation</span>
<div class={styles.navScroller}>
<ul class={styles.navList} role="list">
<For each={workspaceSidebarItems}>
{(item): JSX.Element => {
const Icon = item.icon;
return (
<li>
<button
type="button"
classList={{
[styles.navItem]: true,
[styles.navItemActive]: !!item.active,
}}
>
<Icon class={styles.icon} size={18} strokeWidth={2} />
<span class={styles.label}>{item.label}</span>
<Show when={item.meta}>
<span class={styles.itemMeta}>{item.meta}</span>
</Show>
</button>
</li>
);
}}
</For>
</ul>
</div>
</div>
</aside>
);
};

View File

@@ -0,0 +1,53 @@
// Path: Frontend/src/components/shell/data/shell.data.ts
import type { Component } from "solid-js";
import { Bell, Folder, Home, LayoutGrid, Plus, Search, Settings, User } from "../../../lib/icons";
type ShellIconProps = {
class?: string;
size?: number;
strokeWidth?: number;
};
export type ShellIcon = Component<ShellIconProps>;
export type RailItem = {
id: string;
label: string;
abbreviation: string;
active?: boolean;
};
export type SidebarItem = {
id: string;
label: string;
icon: ShellIcon;
active?: boolean;
meta?: string;
};
export type TopBarAction = {
id: string;
label: string;
icon: ShellIcon;
};
export const railItems: readonly RailItem[] = [
{ id: "personal", label: "Personal", abbreviation: "P" },
{ id: "moku", label: "Moku", abbreviation: "M", active: true },
{ id: "labs", label: "Labs", abbreviation: "L" },
] as const;
export const workspaceSidebarItems: readonly SidebarItem[] = [
{ id: "home", label: "Home", icon: Home, active: true },
{ id: "boards", label: "Boards", icon: LayoutGrid, meta: "0" },
{ id: "docs", label: "Docs", icon: Folder, meta: "0" },
{ id: "settings", label: "Settings", icon: Settings },
] as const;
export const topBarActions: readonly TopBarAction[] = [
{ id: "search", label: "Search", icon: Search },
{ id: "create", label: "Create", icon: Plus },
{ id: "inbox", label: "Inbox", icon: Bell },
{ id: "profile", label: "Profile", icon: User },
] as const;

View File

@@ -0,0 +1,79 @@
.viewport {
--workspace-content-max-width: var(--content-width-wide);
--workspace-card-min-height: calc(var(--space-12) * 3);
min-width: 0;
min-height: 0;
display: grid;
align-content: start;
gap: var(--space-5);
padding: var(--space-5) var(--space-6);
}
.hero {
display: grid;
gap: var(--space-3);
width: 100%;
max-width: var(--workspace-content-max-width);
}
.eyebrow {
@include text-caption;
color: var(--color-text-muted);
text-transform: uppercase;
}
.title {
@include text-display;
font-family: var(--font-family-display);
max-width: 12ch;
}
.description {
max-width: 64ch;
color: var(--color-text-muted);
}
.grid {
width: 100%;
max-width: var(--workspace-content-max-width);
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: var(--space-4);
}
.card {
display: grid;
gap: var(--space-2);
min-height: var(--workspace-card-min-height);
padding: var(--space-4);
border: 1px solid var(--color-border);
border-radius: var(--radius-xl);
background: var(--color-surface);
box-shadow: var(--shadow-soft);
}
.cardTitle {
@include text-title;
}
.cardCopy {
color: var(--color-text-muted);
}
.cardMeta {
@include text-caption;
color: var(--color-text-muted);
}
@include respond-down(tablet) {
.grid {
grid-template-columns: 1fr;
}
}
@include respond-down(mobile) {
.viewport {
gap: var(--space-4);
padding: var(--space-4);
}
}

View File

@@ -0,0 +1,54 @@
// Path: Frontend/src/components/workspace-home/WorkspaceHome/WorkspaceHome.tsx
import { For, type JSX } from "solid-js";
import styles from "./WorkspaceHome.module.scss";
type ShellCheckpointCard = {
title: string;
copy: string;
meta: string;
};
const shellCheckpointCards: readonly ShellCheckpointCard[] = [
{
title: "App shell",
copy: "Top bar, left rail, workspace sidebar, and content viewport are now split into modular components.",
meta: "Layout foundation",
},
{
title: "Workspace context",
copy: "The shell already has clear places for org context, workspace switching, and future surface navigation.",
meta: "Navigation foundation",
},
{
title: "Next build target",
copy: "You can now plug in workspace home content, auth state, and early primitives without redesigning the whole frame.",
meta: "Ready for v0.1.0 work",
},
];
export const WorkspaceHome = (): JSX.Element => {
return (
<main class={styles.viewport}>
<section class={styles.hero}>
<span class={styles.eyebrow}>Workspace home</span>
<h1 class={styles.title}>Moku is ready for its first real shell.</h1>
<p class={styles.description}>
This is the barebone app frame for v0.1.0 enough structure to start building real frontend surfaces on top of a real backend core.
</p>
</section>
<section class={styles.grid} aria-label="Shell checkpoints">
<For each={shellCheckpointCards}>
{(card): JSX.Element => (
<article class={styles.card}>
<h2 class={styles.cardTitle}>{card.title}</h2>
<p class={styles.cardCopy}>{card.copy}</p>
<span class={styles.cardMeta}>{card.meta}</span>
</article>
)}
</For>
</section>
</main>
);
};

View File

@@ -1,4 +1,21 @@
// Path: Frontend/src/entry-client.tsx
// @refresh reload
import type { JSX } from "solid-js";
import { mount, StartClient } from "@solidjs/start/client";
mount(() => <StartClient />, document.getElementById("app")!);
const getAppRoot = (): HTMLElement => {
const appRoot = document.getElementById("app");
if (!(appRoot instanceof HTMLElement)) {
throw new Error("App root element '#app' was not found.");
}
return appRoot;
};
const mountApp = (): void => {
mount((): JSX.Element => <StartClient />, getAppRoot());
};
mountApp();

View File

@@ -1,6 +1,7 @@
// Path: Frontend/src/entry-server.tsx
// @refresh reload
import type { JSX } from "solid-js";
import { createHandler, StartServer } from "@solidjs/start/server";
const themeBootstrapScript = `
@@ -19,22 +20,32 @@ const themeBootstrapScript = `
})();
`;
export default createHandler(() => (
<StartServer
document={({ assets, children, scripts }) => (
<html lang="en" data-theme="light">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
<script innerHTML={themeBootstrapScript} />
{assets}
</head>
<body>
<div id="app">{children}</div>
{scripts}
</body>
</html>
)}
/>
));
type DocumentRenderProps = {
assets?: JSX.Element;
children?: JSX.Element;
scripts?: JSX.Element;
};
const renderDocument = ({ assets, children, scripts }: DocumentRenderProps): JSX.Element => {
return (
<html lang="en" data-theme="light">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
<script innerHTML={themeBootstrapScript} />
{assets}
</head>
<body>
<div id="app">{children}</div>
{scripts}
</body>
</html>
);
};
const serverHandler = createHandler((): JSX.Element => {
return <StartServer document={renderDocument} />;
});
export default serverHandler;

View File

@@ -1 +1,3 @@
// Path: Frontend/src/global.d.ts
/// <reference types="@solidjs/start/env" />

View File

@@ -16,7 +16,7 @@ export const resolvePreferredTheme = (): Theme => {
export const getDocumentTheme = (): Theme => (document.documentElement.getAttribute("data-theme") === "dark" ? "dark" : "light");
export const setTheme = (theme: Theme) => {
export const setTheme = (theme: Theme): void => {
document.documentElement.setAttribute("data-theme", theme);
localStorage.setItem(THEME_STORAGE_KEY, theme);
};

View File

@@ -0,0 +1,11 @@
// Path: Frontend/src/lib/icons/index.ts
export { default as Bell } from "lucide-solid/icons/bell";
export { default as ChevronDown } from "lucide-solid/icons/chevron-down";
export { default as Folder } from "lucide-solid/icons/folder";
export { default as Home } from "lucide-solid/icons/house";
export { default as LayoutGrid } from "lucide-solid/icons/layout-grid";
export { default as Plus } from "lucide-solid/icons/plus";
export { default as Search } from "lucide-solid/icons/search";
export { default as Settings } from "lucide-solid/icons/settings";
export { default as User } from "lucide-solid/icons/user";

View File

@@ -1,6 +1,11 @@
/* Path: Frontend/src/styles/base/_fonts.scss */
:root {
--font-family-sans: "Inter", "Geist", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--font-family-mono: "Geist Mono", "SFMono-Regular", ui-monospace, "Cascadia Code", "Roboto Mono", monospace;
--font-family-sans:
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
--font-family-heading: "Avenir Next", "Segoe UI", "Helvetica Neue", Arial, sans-serif;
--font-family-display: "Avenir Next", "Segoe UI", "Helvetica Neue", Arial, sans-serif;
--font-family-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
--font-family-mono:
ui-monospace, "SF Mono", "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}

View File

@@ -6,6 +6,7 @@ html {
background: var(--color-canvas);
color: var(--color-text);
font-family: var(--font-family-sans);
font-synthesis: none;
scroll-behavior: smooth;
transition:
background-color var(--motion-duration-base) var(--motion-ease-standard),
@@ -15,6 +16,8 @@ html {
body {
background: var(--color-canvas);
color: var(--color-text);
font-family: var(--font-family-sans);
@include text-body;
}
a,
@@ -50,30 +53,48 @@ a {
}
h1 {
font-family: var(--font-family-display);
text-wrap: balance;
@include text-display;
}
h2 {
font-family: var(--font-family-heading);
text-wrap: balance;
@include text-heading;
}
h3 {
font-family: var(--font-family-heading);
text-wrap: balance;
@include text-title;
}
h4,
h5,
h6 {
font-family: var(--font-family-heading);
text-wrap: balance;
@include text-label;
}
p,
span,
li,
blockquote,
figcaption {
text-wrap: pretty;
@include text-body;
}
label,
legend {
@include text-label;
}
button,
input,
textarea,
label,
li {
select {
@include text-body;
}
@@ -82,6 +103,7 @@ pre,
kbd,
samp {
font-family: var(--font-family-mono);
font-size: 0.95em;
}
#app {

View File

@@ -37,6 +37,11 @@
--radius-xl: 1.25rem;
--radius-pill: 999px;
--control-size-md: 2.25rem;
--control-size-lg: 2.5rem;
--content-width-wide: 72rem;
--blur-overlay: 18px;
--shadow-soft: 0 12px 32px hsl(220 30% 10% / 0.08);
--shadow-strong: 0 20px 48px hsl(220 30% 10% / 0.16);
@@ -51,4 +56,35 @@
--motion-duration-base: 220ms;
--motion-duration-slow: 320ms;
--motion-ease-standard: cubic-bezier(0.2, 0.8, 0.2, 1);
// Typography
// ============================
--font-size-caption: 0.75rem;
--font-size-label: 0.875rem;
--font-size-body: 1rem;
--font-size-title: clamp(1.125rem, 1.05rem + 0.3vw, 1.25rem);
--font-size-heading: clamp(1.5rem, 1.2rem + 1vw, 2.125rem);
--font-size-display: clamp(2.25rem, 1.7rem + 2.2vw, 3.75rem);
--line-height-caption: 1.4;
--line-height-label: 1.35;
--line-height-body: 1.55;
--line-height-title: 1.3;
--line-height-heading: 1.15;
--line-height-display: 1.05;
--font-weight-caption: 500;
--font-weight-label: 600;
--font-weight-body: 400;
--font-weight-title: 600;
--font-weight-heading: 600;
--font-weight-display: 700;
--letter-spacing-caption: 0.01em;
--letter-spacing-label: 0.005em;
--letter-spacing-body: 0;
--letter-spacing-title: -0.01em;
--letter-spacing-heading: -0.02em;
--letter-spacing-display: -0.03em;
}

View File

@@ -18,38 +18,85 @@
}
}
@mixin respond-down($breakpoint) {
@if $breakpoint == mobile {
@media (max-width: calc($breakpoint-mobile - 0.01rem)) {
@content;
}
} @else if $breakpoint == tablet {
@media (max-width: calc($breakpoint-tablet - 0.01rem)) {
@content;
}
} @else if $breakpoint == desktop {
@media (max-width: calc($breakpoint-desktop - 0.01rem)) {
@content;
}
}
}
@mixin interactive-frame(
$background: var(--color-surface),
$border: var(--color-border),
$color: var(--color-text-muted),
$radius: var(--radius-md)
) {
border: 1px solid $border;
border-radius: $radius;
background: $background;
color: $color;
}
@mixin interactive-frame-hover(
$background: var(--color-surface-hover),
$border: var(--color-border-strong),
$color: var(--color-text)
) {
&:hover {
background: $background;
border-color: $border;
color: $color;
text-decoration: none;
}
}
@mixin text-caption {
font-size: clamp(0.75rem, 0.72rem + 0.12vw, 0.875rem);
font-weight: 500;
line-height: 1.4;
font-size: var(--font-size-caption);
font-weight: var(--font-weight-caption);
line-height: var(--line-height-caption);
letter-spacing: var(--letter-spacing-caption);
}
@mixin text-label {
font-size: clamp(0.875rem, 0.84rem + 0.15vw, 1rem);
font-weight: 600;
line-height: 1.4;
font-size: var(--font-size-label);
font-weight: var(--font-weight-label);
line-height: var(--line-height-label);
letter-spacing: var(--letter-spacing-label);
}
@mixin text-body {
font-size: clamp(0.95rem, 0.92rem + 0.18vw, 1.05rem);
font-weight: 400;
line-height: 1.6;
font-size: var(--font-size-body);
font-weight: var(--font-weight-body);
line-height: var(--line-height-body);
letter-spacing: var(--letter-spacing-body);
}
@mixin text-title {
font-size: clamp(1.1rem, 1rem + 0.4vw, 1.35rem);
font-weight: 600;
line-height: 1.25;
font-size: var(--font-size-title);
font-weight: var(--font-weight-title);
line-height: var(--line-height-title);
letter-spacing: var(--letter-spacing-title);
}
@mixin text-heading {
font-size: clamp(1.45rem, 1.2rem + 1vw, 2rem);
font-weight: 650;
line-height: 1.1;
font-size: var(--font-size-heading);
font-weight: var(--font-weight-heading);
line-height: var(--line-height-heading);
letter-spacing: var(--letter-spacing-heading);
}
@mixin text-display {
font-size: clamp(2rem, 1.45rem + 2.1vw, 3.5rem);
font-weight: 700;
line-height: 1;
font-size: var(--font-size-display);
font-weight: var(--font-weight-display);
line-height: var(--line-height-display);
letter-spacing: var(--letter-spacing-display);
}

View File

@@ -1,19 +1,21 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"allowJs": true,
"strict": true,
"noEmit": true,
"types": ["vite/client"],
"isolatedModules": true,
"paths": {
"~/*": ["./src/*"]
}
}
"compilerOptions": {
"target": "ESNext",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"allowJs": true,
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"types": ["vite/client", "node"],
"isolatedModules": true,
"paths": {
"~/*": ["./src/*"]
}
}
}

View File

@@ -1,27 +1,4 @@
# Moku Base
Empty scaffold for a Monday-style replacement app.
## Structure
- `Backend/` - blank backend placeholder
- `Commands/` - Just submodules and command entrypoints
- `Docker/` - local compose files
- `Env/` - local environment files
- `Frontend/` - SolidStart bare workspace shell
- `Proxy/` - blank proxy placeholder
## Local usage
```bash
just --list --list-submodules
```
Main local flow:
```bash
just local dev
```
# Moku Work
## License