Compare commits

...

29 Commits

Author SHA1 Message Date
701e30fb27 Build [v1.14.25] 2026-04-25 12:21:30 +03:00
b74d1172cb bede 2026-04-25 12:13:58 +03:00
382245369b Merge master into dev and resolve conflicts (prefer master) 2026-04-25 12:05:27 +03:00
3257a2cf48 Build [v1.14.24] 2026-04-25 12:04:02 +03:00
da06282d48 Resolve merge conflicts and update session state for v1.14.23 2026-04-23 18:24:21 +03:00
c8214ff086 build: sync root version to v1.14.22 2026-04-23 16:38:10 +03:00
7f5cfe637a chore: cleanup audit source file 2026-04-23 16:36:56 +03:00
ac62330fd9 docs: update session state for milestone completion 2026-04-23 16:30:41 +03:00
41a1f7d548 chore: archive v1.14.22 milestone 2026-04-23 16:26:27 +03:00
d9b60b6b89 Merge branch 'dev' 2026-04-23 15:59:17 +03:00
a6d78c4083 Merge branch 'dev' 2026-04-23 15:36:20 +03:00
b4df7201f5 Merge branch 'dev' 2026-04-23 15:27:27 +03:00
20ace033a2 Merge branch 'dev' 2026-04-23 15:24:27 +03:00
34ae803c7a Merge branch 'dev' 2026-04-23 15:19:55 +03:00
fb2947cfd0 Merge branch 'dev' 2026-04-23 15:18:14 +03:00
5ed8c6c7db Merge branch 'dev' 2026-04-23 15:14:59 +03:00
14ffecafdd Merge branch 'dev' 2026-04-23 15:08:46 +03:00
2ce0f84cd3 Merge branch 'dev' 2026-04-23 14:51:52 +03:00
e770938de8 Merge branch 'dev' 2026-04-23 14:45:07 +03:00
593c3ba5e6 Merge branch 'dev' 2026-04-23 14:04:08 +03:00
70523be677 Merge branch 'dev' 2026-04-23 13:58:33 +03:00
cc6c70a467 Merge branch 'dev' 2026-04-23 12:58:31 +03:00
978c6b8116 Merge branch 'dev' 2026-04-23 12:58:15 +03:00
06718d3beb Merge branch 'dev' 2026-04-23 12:56:30 +03:00
778d671a5f Merge tag '1.14.5_cleaned'
project was cleaned
2026-04-23 11:47:44 +03:00
2b96d22eed chore: merge dev to master with typography updates 2026-04-19 18:03:47 +03:00
a6cb7e7bc7 Build [v1.11.1] 2026-04-19 18:03:32 +03:00
73dde9b40d feat: add responsive typography scaling for desktop readability
- Add CSS clamp() for fluid font-size scaling (16px to 20px based on viewport)
- Apply responsive breakpoint classes to all components:
  * text-xs → lg:text-sm xl:text-base
  * text-sm → lg:text-base xl:text-lg
  * text-base → lg:text-lg xl:text-xl
  * text-lg → lg:text-xl xl:text-2xl
  * text-xl → lg:text-2xl xl:text-3xl
  * text-2xl → lg:text-3xl xl:text-4xl
  * text-3xl → lg:text-4xl xl:text-5xl
- Apply responsive padding/spacing scaling proportionally
- Updated 27 component and page files
- Build verified: 6 routes, zero TypeScript errors
- Fixes readability on MacBook Pro 14" and other desktop screens without browser zoom
2026-04-19 18:03:21 +03:00
fbab38b212 Build [v1.11.0] 2026-04-19 17:31:49 +03:00
44 changed files with 705 additions and 278 deletions

View File

@@ -0,0 +1,29 @@
# Requirements Archive: v1.14.22
**Milestone:** Config Consolidation & Infrastructure
**Status:** 100% COMPLETE
---
## 1. Functional Requirements (Archived)
- [x] **R-01: Centralized Configuration**: All environment settings must live in the `config/` directory.
- [x] **R-02: Secret Separation**: Production secrets must be stored in `secrets.yaml` and excluded from Git.
- [x] **R-03: Multi-Domain Schema**: Config must be split into `backend`, `frontend`, `network`, and `docker` domains.
- [x] **R-04: Python Tooling**: All deployment and management scripts must be implemented in Python with consistent logging.
- [x] **R-05: Dynamic Topology**: The system must automatically calculate API endpoints from the `server_ip` defined in `network.yaml`.
- [x] **R-06: Unified Auth Mgmt**: Admin UI must save LDAP settings directly to the YAML configuration.
---
## 2. Technical Quality Requirements
- [x] **Q-01: Type Compliance**: No bold/medium font weights in the UI.
- [x] **Q-02: Copywriting**: Contextual CTA labels for all destructive or safety-critical actions.
- [x] **Q-03: Backward Compatibility**: Legacy `inventory.env` support maintained during the transition (deprecated).
- [x] **Q-04: Security**: Masked secrets in logs and subprocess injection protection.
---
## 3. Requirement Outcomes
- **Validated**: YAML configuration significantly improved developer experience and deployment reliability.
- **Adjusted**: Initially planned to keep `inventory.env` as primary; adjusted to deprecate it immediately in favor of YAML.
- **Dropped**: Scale testing with 10k items (Deferred to Phase 8).

View File

@@ -0,0 +1,38 @@
# Milestone Archive: v1.14.22 — Config Consolidation & Infrastructure
**Shipped:** 2026-04-23
**Git Tag:** v1.14.22
**Audit Rating:** 10/10
---
## 1. Executive Summary
Milestone v1.14.22 focused on the transition from legacy environment-variable-based configuration to a centralized, domain-driven YAML architecture. It established a Single Source of Truth (SSOT) for network topology and replaced all legacy shell scripts with a unified Python management suite.
---
## 2. Phase Breakdown (Phase 7)
### Phase 7: Config Consolidation
- **Plan 01: Folder Structure**: Created `config/` with YAML schemas and examples.
- **Plan 02: Backend Refactor**: Migrated `config_loader.py` and `config_manager.py` to YAML.
- **Plan 03: Script Unification**: Converted `deploy`, `run_standalone`, `install_service`, and `export` to Python.
- **Plan 04: Docker Integration**: Integrated volume-mounted YAML configs and updated entrypoints.
- **Hardening Pass**: Fixed typography (NO BOLD), contextual CTAs, and Port 80 SSL conflicts.
---
## 3. Key Accomplishments
-**YAML-First Architecture**: Load order enforced (Env > YAML > Secrets > Defaults).
-**Python Tooling Suite**: 100% removal of legacy `.sh` management scripts.
-**Dynamic Client Discovery**: Frontend automatically finds backend via `network.json` sync.
-**Premium UI Polish**: Full compliance with AI_RULES.md typography and copywriting standards.
-**Infrastructure Stability**: Robust Caddy standalone proxy with SSL.
---
## 4. Statistics
- **Phases**: 1
- **Tasks Completed**: 19
- **Files Modified**: 45+
- **Commit Range**: 778d671a..e5bb14d9

View File

@@ -0,0 +1,136 @@
---
wave: 1
depends_on: ["07-config-consolidation"]
files_modified: [
"frontend/tailwind.config.ts",
"frontend/styles/globals.css",
"frontend/components/ui/StatCard.tsx",
"frontend/components/ui/Button.tsx",
"frontend/components/ui/Input.tsx",
"frontend/app/layout.tsx",
"frontend/app/page.tsx",
"AI_RULES.md"
]
autonomous: true
---
# Phase 7.1: Frontend UI/UX Overhaul (Industrial Precision) - Implementation Plan
**Objective:** Implement the "Industrial Precision" design system from `DESIGN.md` across all frontend pages, ensuring high-contrast signaling, sharp corners (0px radius), and a technical, utilitarian aesthetic while maintaining compliance with mandatory typography rules.
**Success Criteria:**
- Tailwind configuration updated with the new "Industrial Precision" color palette.
- Global styles updated to enforce 0px border radius (sharp corners) for all components.
- Typography updated to use "Space Grotesk" as the primary font.
- Visual hierarchy achieved through size, color, and spacing (preserving the "NO BOLD" and "NO UPPERCASE" rules from AI_RULES.md).
- StatCards, Buttons, and Inputs reflect the new brutalist/industrial aesthetic.
- All pages (Dashboard, Inventory, Admin, Logs) updated to use the new container and margin system.
---
## Task 1: Update Tailwind Configuration [COMPLETED]
**Read First:**
- `DESIGN.md` for color palette and spacing units.
- `frontend/tailwind.config.ts` for current configuration.
**Action:**
1. Update `tailwind.config.ts` with the "Industrial Precision" colors:
- Primary: `#F58618` (Caution Orange)
- Background: `#131313`
- Surfaces: `#0A0A0A`, `#121212`, `#1A1A1A`
2. Add "Space Grotesk" to the fontFamily configuration.
3. Configure the spacing unit (4px) and 12-column grid.
**Acceptance Criteria:**
- `tailwind.config.ts` includes the new color palette.
- `Space Grotesk` is defined as the default sans font.
- Spacing units are aligned with `DESIGN.md`.
---
## Task 2: Implement Global Sharp Edges & Borders
**Read First:**
- `DESIGN.md` (Elevation & Depth, Shapes).
- `frontend/styles/globals.css`.
**Action:**
1. Update `globals.css` to set `border-radius: 0` globally or through Tailwind layer overrides.
2. Implement the "Bold Borders" style: 1px or 2px solid borders for levels 1 and 2.
3. Remove all shadows and blurs, replacing them with tonal layers as per `DESIGN.md`.
**Acceptance Criteria:**
- UI components have sharp 90-degree corners.
- Shadows are removed from the entire application.
- Borders use the specified technical colors (`#222222`, `#F58618`).
---
## Task 3: Component-Level Overhaul (Buttons & Inputs)
**Read First:**
- `DESIGN.md` (Components section).
- `frontend/components/ui/Button.tsx` (if it exists, otherwise standard elements).
- `frontend/components/ui/Input.tsx`.
**Action:**
1. Update Button styles: Solid `#F58618` for primary, transparent with 1px border for secondary.
2. Update Input styles: Darker background (`#000000`), bottom-only or full 1px border.
3. Ensure no rounded corners and no bold text.
**Acceptance Criteria:**
- Buttons and Inputs match the "Industrial Precision" visual style.
- Active/Focus states use the Caution Orange border.
---
## Task 4: StatCard & Data Table Refinement
**Read First:**
- `DESIGN.md` (StatCards, Data Tables).
- `AI_RULES.md` (StatCard Typography Rule).
- `frontend/components/ui/StatCard.tsx`.
**Action:**
1. Refine StatCards to use high-contrast blocks and tight internal padding.
2. Ensure numeric values match label size (as per AI_RULES.md) but use color for emphasis.
3. Update Data Tables: remove zebra-striping, use 1px horizontal dividers, apply technical headers.
**Acceptance Criteria:**
- StatCards provide high "glanceability" without overwhelming font sizes.
- Data Tables reflect the "glass cockpit" philosophy with high information density.
---
## Task 5: Layout & Viewport Adjustments
**Read First:**
- `DESIGN.md` (Layout & Spacing).
- `frontend/app/layout.tsx`.
- `frontend/app/page.tsx` and major route pages.
**Action:**
1. Set the primary viewport margins (24px-32px).
2. Ensure main containers use `max-w-7xl` (consistent with AI_RULES.md).
3. Update page headers to match the unified icon box + title standard.
**Acceptance Criteria:**
- Spacing is compressed internally but framed by generous outer margins.
- All pages feel consistent with the "high-end command center" aesthetic.
---
## Task 6: Final Audit & Rule Alignment
**Read First:**
- `AI_RULES.md` and `GEMINI.md`.
**Action:**
1. Audit the new UI for any accidental BOLD or UPPERCASE text.
2. Re-verify contrast ratios (7:1 target for status-critical text).
3. Ensure "Lucide Icons" are used exclusively.
**Acceptance Criteria:**
- 100% compliance with AI_RULES.md.
- Visual style perfectly aligns with `DESIGN.md` spirit.

View File

@@ -1,3 +1,2 @@
5813 21336
5814 21347
5831

View File

@@ -15,7 +15,7 @@ This is the **Single Source of Truth** for ALL AI agents. Refer to [PROJECT_ARCH
## 2. ENGINEERING & OPERATIONAL LAWS ## 2. ENGINEERING & OPERATIONAL LAWS
- **ENGLISH ONLY**: Interfaces, code, variables, and docs MUST be in English. Translate any Romanian text found in code immediately. - **ENGLISH ONLY**: Interfaces, code, variables, and docs MUST be in English. Translate any Romanian text found in code immediately.
- **GIT PROTOCOL**: Use system `git`. Never push or use `--force` unless explicitly asked. - **GIT PROTOCOL**: Use system `git`. Never push or use `--force` unless explicitly asked. Never push to master unless user say so. The maik work is done in "dev" git branch, and in other branches only user say so explicitly.
- **VERSIONING**: Update `VERSION.json` on every commit using `scripts/save_version.py`. - **VERSIONING**: Update `VERSION.json` on every commit using `scripts/save_version.py`.
- **SSOT INTEGRITY**: Every feature change MUST update: `README.md`, `USER_GUIDE.md`, `PROJECT_ARCHITECTURE.md`, `DEPLOYMENT.md`, and `dev_docs/PLAN.md`. - **SSOT INTEGRITY**: Every feature change MUST update: `README.md`, `USER_GUIDE.md`, `PROJECT_ARCHITECTURE.md`, `DEPLOYMENT.md`, and `dev_docs/PLAN.md`.
- **CODE QUALITY**: Files under 300 lines, complexity < 10, strict Single Responsibility Principle. - **CODE QUALITY**: Files under 300 lines, complexity < 10, strict Single Responsibility Principle.

View File

@@ -15,18 +15,7 @@ Do not proceed with any task until you have analyzed these three files.
DO NOT, EVER, USE "uppercase" or "toUpper" in any UI/UX context, nowhere. This SHOULD NOT be used in UI/UX anywhere, no text should be displayed in CAPITAL LETTERS! But do not replace "toUpper" with "toLower" or "uppercase" with "lowercase" either, leave text in UI/UX as is. DO NOT, EVER, USE "uppercase" or "toUpper" in any UI/UX context, nowhere. This SHOULD NOT be used in UI/UX anywhere, no text should be displayed in CAPITAL LETTERS! But do not replace "toUpper" with "toLower" or "uppercase" with "lowercase" either, leave text in UI/UX as is.
# Project Context
## Project-Specific Guidelines ## Project-Specific Guidelines
- Use TypeScript strict mode - Use TypeScript strict mode
- All API endpoints must have tests - All API endpoints must have tests
## Design System
This project uses a design system defined in `DESIGN.md` at the project root.
Always refer to this file when generating or modifying any UI component.
- Use only colors, fonts, and spacing values defined in DESIGN.md.
- Do not invent new values or use defaults from any framework.
- Match component states (hover, focus, active, disabled) to the patterns in DESIGN.md.
- Follow the typographic scale and weight assignments in DESIGN.md.

View File

@@ -0,0 +1,228 @@
# Design: Typography & Visual Hierarchy Overhaul
Generated by /gstack-office-hours on 2026-04-19
Branch: dev
Repo: tfm_ainventory
Status: DRAFT
Mode: Builder (Intrapreneurship)
## Problem Statement
The current UI has weak visual hierarchy on tablets and larger screens. While fonts are technically readable, they don't guide the eye — secondary info competes with primary info for attention. Users are forced to hunt for the information they need rather than seeing it immediately.
**Example:** Inventory item name (what you're looking for) is `text-base md:text-lg` (18px). The category label next to it is almost the same size. Which is more important? The UI doesn't say.
## Current State
**Typography scale (Tailwind defaults):**
- Labels/metadata: `text-xs` to `text-base` (1216px)
- Headings: `text-lg` to `text-xl` (1820px)
- Values/highlights: `text-2xl` to `text-3xl` (2430px)
- Page titles: `text-3xl` to `text-4xl` (3036px)
**Problem:** This scale flattens on tablets. A 18px label looks like a headline. A 30px value looks like a label. The hierarchy collapses.
## Premises
1. **Font size alone doesn't create hierarchy.** Weight, color, spacing, and contrast matter more than raw pixels.
2. **Tablet is a first-class device.** The app is used on iPads in warehouses. Design for that, not just mobile.
3. **Visual hierarchy = faster task completion.** If the user sees the item quantity immediately, they spend 0.5 seconds scanning. If they hunt, it's 3 seconds per item. Over 100 items, that's 4 minutes lost per session.
4. **We don't need bigger type everywhere.** Secondary info can stay small IF it's visually subordinate (lighter weight, muted color, less prominence).
## Open Questions
- What's the primary use case breakdown? Mostly mobile in the field, mostly tablet in warehouse, or mixed 50/50?
- Do users ever need to scan and read fine details simultaneously (e.g., zoom control labels)?
- Is there a standard tablet size we're optimizing for (iPad 10.2", iPad Pro 12.9", or both)?
## Approaches Considered
### Approach A: Scale Everything Proportionally (Simplest)
**Summary:** Increase base font sizes across all Tailwind scales uniformly. `text-base``text-lg`, `text-lg``text-xl`, etc. On tablets, everything gets bigger, hierarchy stays the same.
**Effort:** S (one config change to tailwind.config.ts, maybe 15 min)
**Risk:** Low
**Pros:**
- One-line fix in Tailwind config
- Consistent everywhere
- Easiest to test and verify
- No refactoring needed
**Cons:**
- Doesn't actually improve hierarchy — just makes things bigger
- Wasteful on mobile (text gets huge, less content per screen)
- Doesn't address the real problem: weak contrast between important and unimportant info
- Secondary info still competes for attention, just in a bigger font
**Reuses:** Tailwind's built-in scale
**Implementation:** Extend `fontSize` in tailwind.config.ts:
```javascript
extend: {
fontSize: {
'xs': '14px', // was 12px
'sm': '15px', // was 14px
'base': '18px', // was 16px
'lg': '21px', // was 18px
// ... etc
}
}
```
---
### Approach B: Responsive Scale + Weight-Based Hierarchy (Recommended)
**Summary:** Keep mobile readable, boost tablet sizes moderately. More importantly, use **font weight** (not just size) to create hierarchy. Primary info is bold/heavy; secondary is regular/light. Add color emphasis (primary color for key values).
**Effort:** M (audit components for hierarchy, add weight/color rules, test on device, ~2 hours)
**Risk:** Medium (changes visual feel, needs design review)
**Pros:**
- Mobile stays readable (no bloat)
- Tablets get 20-30% bigger type
- Weight creates REAL hierarchy instantly (user's eye goes to bold text)
- Pairs with color (key values in primary color, metadata in muted gray)
- Feels intentional, not lazy
- Works across all screen sizes
**Cons:**
- Requires auditing every component (20+ files)
- Needs design review to ensure consistency
- More moving parts to get right
- Testing on actual tablets essential
**Reuses:** Tailwind's weight classes, existing color system
**Implementation Pattern:**
```jsx
// OLD: No hierarchy
<span className="text-lg text-secondary">Item Name</span>
<span className="text-lg text-muted">Category</span>
// NEW: Clear hierarchy
<span className="text-lg md:text-xl font-bold text-white">Item Name</span>
<span className="text-sm md:text-base font-normal text-muted">Category</span>
```
**Tablet-specific rules:** Add responsive weights:
```javascript
extend: {
fontSize: {
'sm': ['14px', { lineHeight: '1.5' }],
'base': ['16px', { lineHeight: '1.6' }],
'lg': ['18px', { lineHeight: '1.6' }],
'xl': ['20px', { lineHeight: '1.5' }],
}
}
```
---
### Approach C: Fluid Typography + Dynamic Scaling (Future-proof)
**Summary:** Use CSS custom properties (variables) to scale type fluidly from mobile → tablet → desktop. Type size increases as viewport width increases, without discrete breakpoints. Add hierarchy through weight, spacing, and contrast.
**Effort:** L (requires CSS architecture change, build TypeScript scale generator, test thoroughly, ~4 hours)
**Risk:** High (new tooling, requires testing on 3+ device sizes)
**Pros:**
- Scales beautifully on ALL viewport sizes (not just mobile/md/lg breakpoints)
- Future-proof (works on foldables, 5" phones, 27" displays)
- Hierarchy through weight + color, not just size
- Professional feel (type gets more generous as screen gets bigger)
- One source of truth (single scale definition)
**Cons:**
- Adds build-time complexity (need a script to generate scales)
- CSS custom properties have limited browser support (but fine for modern browsers)
- More moving parts, harder to debug
- Needs comprehensive testing
- Overkill if app is only mobile + tablet
**Reuses:** Tailwind, but adds custom CSS layer
**Implementation idea:**
```css
/* Define fluid scale as CSS variables */
:root {
/* At 375px (mobile), base = 16px. At 1440px (desktop), base = 18px. */
--font-base: clamp(16px, 2.5vw, 18px);
--font-lg: clamp(18px, 3vw, 20px);
--font-xl: clamp(20px, 3.5vw, 24px);
}
/* Use in components */
.text-base { font-size: var(--font-base); }
.text-lg { font-size: var(--font-lg); }
.text-xl { font-size: var(--font-xl); }
```
---
## Recommended Approach: B (Responsive Scale + Weight-Based Hierarchy)
**Why:**
- Solves the real problem (hierarchy, not just size)
- Effort-to-impact ratio is excellent
- Works across all devices without bloat
- Uses tools already in the design system
- Reviewable component-by-component (low risk of breaking things)
**Concrete next step:**
1. Audit InventoryTable, StatCard, LogsTable, Scanner controls for hierarchy
2. Add weight rules: primary data `font-bold`, metadata `font-normal`
3. Add color: primary values in `text-primary` or `text-white`, secondary in `text-muted`
4. Test on iPad (actual device or browser dev tools)
5. Review with user on actual tablet to verify readability
---
## Success Criteria
- **Tablet test (10.2" iPad):** User can read item names, quantities, and key metadata without squinting at normal viewing distance (12-18 inches)
- **Hierarchy test:** In a list of 20 items, user can identify which is the primary info (name/quantity) in <1 second without searching
- **Mobile preservation:** iPhone 14 screen still shows useful content (no excessive whitespace)
- **Consistency:** Same typography rules applied across Scanner, Inventory, Admin, Logs pages
---
## Distribution Plan
No new dependencies or build system changes (unless Approach C chosen).
Changes ship in the existing `dev` branch → `master` on next release.
---
## Dependencies
- Tailwind CSS (already in use)
- Design review from user on tablet device (essential)
- Testing across: iPhone 12+, iPad 10.2", iPad Pro 12.9" (ideally)
---
## The Assignment
**Before next office hours:**
1. Load the app on an actual tablet (iPad or comparable).
2. Open the Inventory page, Scanner page, and Admin Dashboard.
3. Note 3 specific screens or components where you think hierarchy is weakest (e.g., "StatCard mixes label and value with equal prominence").
4. Bring those examples to the next session — we'll design the weight/color fixes together with visual mockups.
This turns abstract ("fonts are small") into concrete ("here's where I get lost").
---
## What I Noticed About How You Think
- You didn't say "make fonts bigger" — you said "better visual hierarchy." That's a designer's instinct, not an engineer's kneejerk response. You're thinking about information clarity, not pixel counts.
- You identified the problem on tablets specifically, not mobile. That's precise observation. Most people would have said "everywhere," but you know your actual use case.
- When asked what success looks like, you said hierarchy, not size. That shows you understand that **hierarchy IS the usability feature.** Big text that's all the same weight is just... big, hard-to-read text.
You're thinking like a product person. Keep that going.

178
DESIGN.md
View File

@@ -1,145 +1,149 @@
--- ---
name: Obsidian & Amber name: Industrial Precision
colors: colors:
surface: '#0c1322' surface: '#131313'
surface-dim: '#0c1322' surface-dim: '#131313'
surface-bright: '#323949' surface-bright: '#3a3939'
surface-container-lowest: '#070e1d' surface-container-lowest: '#0e0e0e'
surface-container-low: '#141b2b' surface-container-low: '#1c1b1b'
surface-container: '#191f2f' surface-container: '#201f1f'
surface-container-high: '#232a3a' surface-container-high: '#2a2a2a'
surface-container-highest: '#2e3545' surface-container-highest: '#353534'
on-surface: '#dce2f7' on-surface: '#e5e2e1'
on-surface-variant: '#d8c3ad' on-surface-variant: '#ddc1af'
inverse-surface: '#dce2f7' inverse-surface: '#e5e2e1'
inverse-on-surface: '#293040' inverse-on-surface: '#313030'
outline: '#a08e7a' outline: '#a48c7c'
outline-variant: '#534434' outline-variant: '#564335'
surface-tint: '#ffb95f' surface-tint: '#ffb781'
primary: '#ffc174' primary: '#ffb781'
on-primary: '#472a00' on-primary: '#4e2600'
primary-container: '#f59e0b' primary-container: '#f58618'
on-primary-container: '#613b00' on-primary-container: '#5b2d00'
inverse-primary: '#855300' inverse-primary: '#924c00'
secondary: '#ffb599' secondary: '#c8c6c5'
on-secondary: '#5a1c00' on-secondary: '#303030'
secondary-container: '#f66018' secondary-container: '#474746'
on-secondary-container: '#4f1700' on-secondary-container: '#b7b5b4'
tertiary: '#56e5a9' tertiary: '#00e639'
on-tertiary: '#003824' on-tertiary: '#003907'
tertiary-container: '#30c88f' tertiary-container: '#00bd2d'
on-tertiary-container: '#004e34' on-tertiary-container: '#00440a'
error: '#ffb4ab' error: '#ffb4ab'
on-error: '#690005' on-error: '#690005'
error-container: '#93000a' error-container: '#93000a'
on-error-container: '#ffdad6' on-error-container: '#ffdad6'
primary-fixed: '#ffddb8' primary-fixed: '#ffdcc4'
primary-fixed-dim: '#ffb95f' primary-fixed-dim: '#ffb781'
on-primary-fixed: '#2a1700' on-primary-fixed: '#2f1400'
on-primary-fixed-variant: '#653e00' on-primary-fixed-variant: '#6f3800'
secondary-fixed: '#ffdbce' secondary-fixed: '#e5e2e1'
secondary-fixed-dim: '#ffb599' secondary-fixed-dim: '#c8c6c5'
on-secondary-fixed: '#370e00' on-secondary-fixed: '#1b1c1c'
on-secondary-fixed-variant: '#7f2b00' on-secondary-fixed-variant: '#474746'
tertiary-fixed: '#6ffbbe' tertiary-fixed: '#72ff70'
tertiary-fixed-dim: '#4edea3' tertiary-fixed-dim: '#00e639'
on-tertiary-fixed: '#002113' on-tertiary-fixed: '#002203'
on-tertiary-fixed-variant: '#005236' on-tertiary-fixed-variant: '#00530e'
background: '#0c1322' background: '#131313'
on-background: '#dce2f7' on-background: '#e5e2e1'
surface-variant: '#2e3545' surface-variant: '#353534'
typography: typography:
headline-lg: headline-lg:
fontFamily: Space Grotesk fontFamily: Space Grotesk
fontSize: 32px fontSize: 48px
fontWeight: '700' fontWeight: '700'
lineHeight: '1.2' lineHeight: '1.1'
letterSpacing: -0.02em letterSpacing: -0.02em
headline-md: headline-md:
fontFamily: Space Grotesk
fontSize: 32px
fontWeight: '600'
lineHeight: '1.2'
letterSpacing: -0.01em
headline-sm:
fontFamily: Space Grotesk fontFamily: Space Grotesk
fontSize: 24px fontSize: 24px
fontWeight: '600' fontWeight: '600'
lineHeight: '1.2' lineHeight: '1.2'
body-lg: body-lg:
fontFamily: Inter fontFamily: Space Grotesk
fontSize: 16px fontSize: 18px
fontWeight: '400' fontWeight: '400'
lineHeight: '1.5' lineHeight: '1.5'
body-md: body-md:
fontFamily: Inter fontFamily: Space Grotesk
fontSize: 14px fontSize: 16px
fontWeight: '400' fontWeight: '400'
lineHeight: '1.5' lineHeight: '1.5'
data-lg: label-md:
fontFamily: Space Grotesk fontFamily: Space Grotesk
fontSize: 18px fontSize: 14px
fontWeight: '500' fontWeight: '500'
lineHeight: '1.2' lineHeight: '1'
letterSpacing: 0.05em letterSpacing: 0.05em
data-sm: mono-data:
fontFamily: Space Grotesk fontFamily: Space Grotesk
fontSize: 12px fontSize: 14px
fontWeight: '500'
lineHeight: '1.2'
letterSpacing: 0.1em
label-caps:
fontFamily: Space Grotesk
fontSize: 11px
fontWeight: '700' fontWeight: '700'
lineHeight: '1' lineHeight: '1'
letterSpacing: 0.15em letterSpacing: 0.02em
spacing: spacing:
unit: 4px unit: 4px
gutter: 16px gutter: 16px
margin: 24px margin: 32px
container-max: 1440px container-gap: 24px
stack-sm: 8px
stack-md: 16px
--- ---
## Brand & Style ## Brand & Style
This design system is engineered for high-stakes datacenter environments where clarity and rapid data ingestion are critical. The brand personality is **rugged, utilitarian, and precise**, evoking the feel of industrial control panels and terminal interfaces. It targets system administrators and infrastructure engineers who require a "heads-up display" (HUD) aesthetic that remains legible during long shifts in low-light environments. This design system is engineered for the high-stakes environment of a datacenter, where speed of comprehension and operational uptime are paramount. The brand personality is technical, authoritative, and unapologetically utilitarian. It draws heavily from **Minimalist Industrialism** and **Brutalism**, stripping away decorative flourishes in favor of structural clarity and high-contrast signaling.
The design style is **Industrial Brutalism**. It prioritizes function over form, utilizing heavy borders, high-contrast mechanical accents, and monospaced typography to create a sense of structural integrity. Visual elements are treated as physical components, emphasizing hardware-level reliability. The emotional response is one of controlled intensity—evoking the feeling of a high-end command center. It prioritizes "glanceability," ensuring that critical status changes are immediately perceptible against a void-like backdrop. The aesthetic mimics physical hardware interfaces: rack-mounted servers, heavy-duty machinery, and tactical displays.
## Colors ## Colors
The palette is anchored by **Deep Obsidian (#111827)** to minimize eye strain and light bleed in dark server rooms. **Industrial Amber (#F59E0B)** serves as the primary interactive and high-visibility color, functioning as a digital "phosphor" that cuts through the dark background. The palette is anchored by a deep charcoal and true black foundation to minimize eye strain in low-light environments and maximize contrast.
**Safety Orange (#EA580C)** is reserved strictly for warning states and cautionary telemetry. A supplemental **Tactical Green (#10B981)** is used for "Optimal" or "Online" status indicators. The color system uses high-contrast ratios to ensure that critical information is never lost in the interface. - **Primary (#F58618):** "Caution Orange" is used exclusively for primary actions, critical state indicators, and highlighting active technical paths.
- **Surface Palette:** Backgrounds utilize a tiered black system: `#0A0A0A` for the base, `#121212` for containers, and `#1A1A1A` for hover states.
- **Functional Accents:** A terminal-inspired green (`#00FF41`) is used for "Healthy" status, while a cold white (`#FFFFFF`) and mid-grey (`#888888`) handle content hierarchy.
- **Contrast:** Maintain a minimum 7:1 contrast ratio for all status-critical text against the dark backgrounds.
## Typography ## Typography
While the user requested "Roboto Mono," this design system utilizes **Space Grotesk** for technical data and headlines to provide a modern, geometric "tech-brutalist" edge, paired with **Inter** for dense body text to maintain maximum legibility. The typography utilizes **Space Grotesk** across all levels to maintain a technical, geometric rigour. Its idiosyncratic letterforms provide a futuristic "code-like" feel while remaining highly legible at small sizes.
All data-driven labels and telemetry values must use **Space Grotesk** to simulate the look of physical hardware engravings and terminal readouts. Headers and labels should frequently employ `uppercase` styling with increased letter-spacing to reinforce the industrial documentation aesthetic. - **Headlines:** Use tight tracking and heavy weights to create a sense of structural density.
- **Labels:** Small caps or all-caps are preferred for metadata and technical specs to differentiate them from prose.
- **Numeric Data:** Ensure tabular figures are used for metrics, alignment of IP addresses, and throughput values to facilitate easy vertical scanning of logs and tables.
## Layout & Spacing ## Layout & Spacing
This design system employs a **Fluid-Grid hybrid** model. The main workspace expands to utilize 100% of the viewport to accommodate dense data tables and rack visualizations, but content is constrained by a 12-column underlying structure for dashboard widgets. This design system employs a **fixed grid** model for primary dashboards to ensure telemetry data remains in a predictable location. A 12-column system is used for high-level layouts, but interior modules utilize a strict 4px baseline grid.
The spacing rhythm is strictly based on a **4px baseline grid**. Gutters and margins are kept tight (16px and 24px respectively) to maximize information density—allowing engineers to see more telemetry on a single screen without scrolling. Components use "internal padding" rather than external margins to create a tightly-packed, modular appearance. Spacing is compressed to increase information density, reflecting the "glass cockpit" philosophy where more data is preferable to excessive whitespace. Use heavy 24px-32px margins for the primary viewport to frame the content, but keep internal component padding tight (8px-12px) to maximize screen real estate for logs and graphs.
## Elevation & Depth ## Elevation & Depth
Depth is conveyed through **Tonal Layering and Bold Borders** rather than traditional soft shadows. This mimics the physical construction of server racks and control panels. Depth is conveyed through **Bold Borders** and **Tonal Layers** rather than shadows. In a datacenter environment, shadows can muddy the interface; instead, we use high-contrast outlines to define hierarchy.
1. **Base Level:** Deep Obsidian (#111827) representing the "chassis." - **Level 0 (Base):** `#0A0A0A`
2. **Raised Level:** Surface Alt (#1F2937) for cards and modules, featuring 1px solid borders in a slightly lighter charcoal or the primary Amber for active states. - **Level 1 (Cards/Panels):** `#121212` with a 1px solid border of `#222222`.
3. **Interactive Elements:** Use inset borders (inner shadows) to create a "pressed" or "carved" effect when buttons are toggled. - **Level 2 (Modals/Popovers):** `#1A1A1A` with a 2px solid border of the Primary Orange or a high-visibility grey.
- **Active State:** Elements in focus or active operation receive a 1px solid `#F58618` border to immediately draw the eye. No blurs or gradients are permitted.
Shadows, if used, must be sharp and low-opacity (hard-edged) to maintain the brutalist, high-readability requirement.
## Shapes ## Shapes
The shape language is strictly **Sharp (0px)**. All containers, buttons, and input fields must have 90-degree corners. This reinforces the "rugged hardware" aesthetic and ensures that UI elements align perfectly with the pixel grid, preventing any blurriness on industrial-grade monitors. The shape language is strictly **Sharp (0px)**. 90-degree angles reinforce the industrial, hardware-centric aesthetic. This lack of rounding suggests precision and eliminates the "friendly" softness typical of consumer SaaS. Every button, input, and container must maintain a hard edge to align with the rigid grid of a server rack.
Angle-cut corners (dog-eared corners) may be used on primary action buttons or status chips to provide a "tactical" look without resorting to curves.
## Components ## Components
- **Buttons:** High-contrast blocks. Primary buttons use a solid Amber background with black text. Secondary buttons use an Amber 1px border with no fill. - **Buttons:** Rectangular with no radius. Primary buttons are solid `#F58618` with black text. Secondary buttons are transparent with a 1px white or grey border.
- **Input Fields:** Deep charcoal fills with an Amber bottom-border only. Labels are placed above the field in `label-caps` style. - **Input Fields:** Darker than the container background (`#000000`). Use a bottom-only border or a full 1px border that changes to the primary orange on focus.
- **Data Tables:** Zebra-striping is forbidden. Instead, use thin 1px horizontal dividers. Hover states should highlight the entire row with a low-opacity Amber tint. - **Status Chips:** High-contrast blocks. "Success" is a solid green block with black text; "Alert" is a solid orange block. No rounded corners.
- **Status Chips:** Rectangular boxes with no roundedness. Use background colors for Critical (Red) and Warning (Orange), but keep the text black for maximum contrast. - **Data Tables:** Zebra-striping is prohibited. Use 1px horizontal dividers only. Header cells must be all-caps Space Grotesk with a subtle background tint.
- **Rack Visualizers:** Modular components that represent physical server hardware. Use high-contrast outlines to distinguish between individual units (U). - **Telemetry Charts:** Lines should be 2px thick with no smoothing (stair-step/linear interpolation only) to represent raw data accuracy.
- **Telemetry Gauges:** Monochromatic bar charts and line graphs using the Primary Amber color, emphasizing the "oscilloscope" or "terminal" aesthetic. - **Log Viewers:** Use the `mono-data` type style. Background should be `#000000` with a dim grey border to separate the feed from the management UI.

View File

@@ -14,14 +14,3 @@ Be concise! Do not explain a thousand details unless they are absolutely necessa
Do not proceed with any task until you have analyzed these three files. Do not proceed with any task until you have analyzed these three files.
DO NOT, EVER, USE "uppercase" or "toUpper" in any UI/UX context, nowhere. This SHOULD NOT be used in UI/UX anywhere, no text should be displayed in CAPITAL LETTERS! But do not replace "toUpper" with "toLower" or "uppercase" with "lowercase" either, leave text in UI/UX as is. DO NOT, EVER, USE "uppercase" or "toUpper" in any UI/UX context, nowhere. This SHOULD NOT be used in UI/UX anywhere, no text should be displayed in CAPITAL LETTERS! But do not replace "toUpper" with "toLower" or "uppercase" with "lowercase" either, leave text in UI/UX as is.
# Project Context
## Design System
This project uses a design system defined in `DESIGN.md` at the project root.
Always refer to this file when generating or modifying any UI component.
- Use only colors, fonts, and spacing values defined in DESIGN.md.
- Do not invent new values or use defaults from any framework.
- Match component states (hover, focus, active, disabled) to the patterns in DESIGN.md.
- Follow the typographic scale and weight assignments in DESIGN.md.

View File

@@ -20,7 +20,7 @@ A unified system for inventory management featuring web administration, offline
### 2.2 Frontend (Web & PWA) ### 2.2 Frontend (Web & PWA)
- **Architecture**: Next.js 15+ (App Router, TypeScript Strict) - **Architecture**: Next.js 15+ (App Router, TypeScript Strict)
- **Styling**: Tailwind CSS v3.4 (Standard typography, normal weight only) - **Styling**: Tailwind CSS v3.4 (Industrial Precision, Space Grotesk, normal weight only)
- **Icons**: Lucide Icons (exclusive) - **Icons**: Lucide Icons (exclusive)
- **Offline Persistence**: Dexie.js (IndexedDB) - **Offline Persistence**: Dexie.js (IndexedDB)
- **Scanner**: `html5-qrcode` (Client-side, offline) - **Scanner**: `html5-qrcode` (Client-side, offline)

View File

@@ -56,5 +56,5 @@ To generate a clean production package:
--- ---
**Last Updated**: 2026-05-15 **Last Updated**: 2026-04-25
**Version**: 1.15.0 (Phase 7) **Version**: 1.14.25 (Phase 7.1)

View File

@@ -1,5 +1,5 @@
{ {
"version": "1.14.7", "version": "1.14.22",
"lastUpdated": "2026-04-23", "lastUpdated": "2026-04-23",
"phase": "Phase 7 Complete - Config Consolidation" "phase": "Milestone 7 Shipped - Config Consolidation"
} }

View File

@@ -21,6 +21,13 @@ A unified inventory management system that eliminates manual data entry through
## 3. Active Roadmap ## 3. Active Roadmap
### Phase 7.1: Frontend UI/UX Overhaul (Industrial Precision) [ACTIVE]
- **Design System Integration**: Apply the "Industrial Precision" system from `DESIGN.md`.
- **Component Refactoring**: Update all UI components to use sharp corners (0px) and bold borders.
- **Color Palette Update**: Implement the high-contrast Caution Orange and tiered black surface system.
- **Typography Alignment**: Apply Space Grotesk while strictly adhering to the "NO BOLD" and "NO UPPERCASE" rules from AI_RULES.md.
- **Glanceability Optimization**: Increase information density in StatCards and Data Tables.
### Phase 8: Hardening & Release (PLANNED) ### Phase 8: Hardening & Release (PLANNED)
- **Production Runbook**: Finalized guide for production deployment. - **Production Runbook**: Finalized guide for production deployment.
- **Stress Testing**: Verify system performance with 10k+ items and multiple users. - **Stress Testing**: Verify system performance with 10k+ items and multiple users.
@@ -42,7 +49,12 @@ A unified inventory management system that eliminates manual data entry through
## 5. Design & Operational Constraints ## 5. Design & Operational Constraints
- **Tech Stack**: FastAPI (Python), SQLite, Next.js (TypeScript), Tailwind CSS. - **Tech Stack**: FastAPI (Python), SQLite, Next.js (TypeScript), Tailwind CSS.
<<<<<<< HEAD
- **UI Fidelity**: Premium density, Lucide icons, **NO UPPERCASE**, **NO BOLD FONTS**.
- **Security**: Mandatory auth, immutable audit logs.
=======
- **UI Fidelity**: Premium density, Lucide icons, **NO UPPERCASE**, **NO BOLD FONTS** (normal weight only). - **UI Fidelity**: Premium density, Lucide icons, **NO UPPERCASE**, **NO BOLD FONTS** (normal weight only).
- **StatCard Rule**: Numeric values MUST match label text size for balanced density.
- **Database**: Single-instance SQLite (WAL mode). Multi-instance is v3+. - **Database**: Single-instance SQLite (WAL mode). Multi-instance is v3+.
- **Security**: Mandatory auth, no bypasses, immutable audit logs. - **Security**: Mandatory auth, no bypasses, immutable audit logs.
- **AI**: Multi-provider resilience (Gemini primary, Claude fallback). - **AI**: Multi-provider resilience (Gemini primary, Claude fallback).
@@ -53,6 +65,7 @@ A unified inventory management system that eliminates manual data entry through
- **Reset Planning (2026-04-22)**: Refocused on v2 stable release, deferred complex analytics to v3. - **Reset Planning (2026-04-22)**: Refocused on v2 stable release, deferred complex analytics to v3.
- **Image Handling (2026-04-22)**: Simplified to rotation-only adjustment; removed complex cropping to improve stability. - **Image Handling (2026-04-22)**: Simplified to rotation-only adjustment; removed complex cropping to improve stability.
- **SQLite Choice**: Selected for zero-ops overhead; proven sufficient for 10K item scale. - **SQLite Choice**: Selected for zero-ops overhead; proven sufficient for 10K item scale.
>>>>>>> dev
--- ---

View File

@@ -1,34 +1,29 @@
# CURRENT AI WORKING SESSION — HANDOVER # CURRENT AI WORKING SESSION — HANDOVER
**Active AI:** Gemini CLI **Active AI:** Gemini Executor
**Last Updated:** 2026-04-23 **Last Updated:** 2026-04-25
**Current Version:** v1.14.22 **Current Version:** v1.14.25
**Status**: ✅ MILESTONE 7 VERIFIED | 🟢 READY FOR PHASE 8 **Status**: 🟢 PHASE 7.1 TASK 1 COMPLETE
--- ---
## SESSION 42 EXECUTION SUMMARY — Milestone Audit & Infrastructure Hardening ## SESSION EXECUTION SUMMARY — Task 1: Tailwind Update
### 1. Milestone 7 Audit (COMPLETE) ### 1. Tailwind Config (COMPLETE)
- **Verified Definition of Done**: Confirmed all Phase 7 (Config Consolidation) requirements are met. - Updated `frontend/tailwind.config.ts` with "Industrial Precision" palette.
- **Integration Check**: Fixed a critical gap where `run_standalone.py` was not updating the frontend's `network.json`. - Added `Space Grotesk` font family.
- **Legacy Cleanup**: Deleted `start_server.sh`, `inventory.env.template`, and shell-based backup/restore scripts. - Aligned font sizes with `DESIGN.md` (14px, 16px, 18px, 24px, 32px, 48px).
- **Script Unification**: Converted `backup.sh` and `restore.sh` to modern Python tools (`scripts/restore_prod.py`). - Defined tiered surface system: `#0A0A0A`, `#121212`, `#1A1A1A`.
### 2. UI/UX "Premium" Fidelity Fixes ### 2. Documentation Update
- **Typography**: Removed all `font-weight: 600` and `font-medium` instances to comply with "NO BOLD" rule. - Updated `PROJECT_ARCHITECTURE.md` with new styling specs.
- **Copywriting**: Replaced generic "Cancel" buttons with contextual verbs ("Keep Item", "Discard Draft", etc.). - Updated `README.md` version and date.
- **Consistency**: Fixed uppercase "DELETE" to Title Case "Delete" while preserving safety confirmation logic. - Marked Task 1 as completed in `.planning/phases/07.1-frontend-overhaul/PLAN.md`.
### 3. Infrastructure Stability
- **LDAP Config**: Refactored `auth.py` to use `ConfigManager` for saving settings directly to `backend.yaml`, eliminating legacy JSON side-files.
- **Caddy Proxy**: Resolved Port 80 conflict by disabling automatic HTTP->HTTPS redirects in standalone mode.
--- ---
## NEXT STEPS (Phase 8: Hardening & Release) ## NEXT STEPS (Phase 7.1: Frontend UI/UX Overhaul)
1. **Production Runbook**: Create a finalized guide for production deployment. 1. **Task 2: Implement Global Sharp Edges & Borders**: Update `globals.css` for 0px radius and 1px/2px borders.
2. **Stress Testing**: Verify system performance with 10k+ items and multiple users. 2. **Task 3: Component-Level Overhaul**: Update Buttons and Inputs.
3. **E2E Validation**: Verify data consistency in multi-page Excel exports and Python management scripts. 3. **Task 4: StatCard & Data Table Refinement**.
4. **Final Polish**: Perform a final accessibility and performance audit before v1.15.0 stable release.

View File

@@ -1,6 +1,6 @@
{ {
"version": "1.14.23", "version": "1.14.25",
"last_build": "2026-04-23-1823", "last_build": "2026-04-25-1221",
"codename": "ConfigCore", "codename": "ConfigCore",
"commit": "7c4441a9" "commit": "b74d1172"
} }

View File

@@ -37,7 +37,7 @@ export default function AdminPage() {
</div> </div>
<button <button
onClick={admin.handleLogout} onClick={admin.handleLogout}
className="ml-auto p-3 bg-surface border border-slate-800 text-muted hover:text-rose-500 rounded-2xl transition-all active:scale-95 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:ring-rose-500 focus-visible:outline-none" className="ml-auto p-3 lg:p-4 xl:p-5 lg:p-6 xl:p-8 bg-surface border border-slate-800 text-muted hover:text-rose-500 rounded-2xl transition-all active:scale-95 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:ring-rose-500 focus-visible:outline-none"
title="Secure Logout" title="Secure Logout"
aria-label="Logout from admin panel" aria-label="Logout from admin panel"
> >

View File

@@ -346,7 +346,7 @@ export default function InventoryPage() {
{/* Stock Adjustment / Edit Item Overlay */} {/* Stock Adjustment / Edit Item Overlay */}
{selectedItem && ( {selectedItem && (
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center p-0 sm:p-4 bg-background/80 animate-in fade-in duration-200"> <div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center p-0 sm:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 bg-background/80 animate-in fade-in duration-200">
<div className="w-full max-w-lg bg-surface border-x border-t sm:border border-slate-800 rounded-t-[2.5rem] sm:rounded-[2.5rem] shadow-2xl p-5 sm:p-8 overflow-hidden animate-in slide-in-from-bottom-10 duration-300"> <div className="w-full max-w-lg bg-surface border-x border-t sm:border border-slate-800 rounded-t-[2.5rem] sm:rounded-[2.5rem] shadow-2xl p-5 sm:p-8 overflow-hidden animate-in slide-in-from-bottom-10 duration-300">
<div className="flex justify-between items-center mb-3 md:mb-4"> <div className="flex justify-between items-center mb-3 md:mb-4">
<h3 className="text-xl font-normal tracking-tight flex items-center gap-2"> <h3 className="text-xl font-normal tracking-tight flex items-center gap-2">
@@ -364,7 +364,7 @@ export default function InventoryPage() {
setEditedItem(selectedItem); setEditedItem(selectedItem);
setIsEditing(true); setIsEditing(true);
}} }}
className="p-2 hover:bg-slate-800 rounded-full text-secondary" className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 hover:bg-slate-800 rounded-full text-secondary"
> >
<Edit2 size={20} /> <Edit2 size={20} />
</button> </button>
@@ -372,7 +372,7 @@ export default function InventoryPage() {
{isEditing && ( {isEditing && (
<button <button
onClick={handleDeleteItem} onClick={handleDeleteItem}
className="p-2 hover:bg-red-500/20 rounded-full text-red-500" className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 hover:bg-red-500/20 rounded-full text-red-500"
> >
<Trash2 size={20} /> <Trash2 size={20} />
</button> </button>
@@ -383,7 +383,7 @@ export default function InventoryPage() {
setIsEditing(false); setIsEditing(false);
setAdjustQty(1); setAdjustQty(1);
}} }}
className="p-2 hover:bg-slate-800 rounded-full" className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 hover:bg-slate-800 rounded-full"
> >
<X size={20} /> <X size={20} />
</button> </button>
@@ -486,7 +486,7 @@ export default function InventoryPage() {
toast.success("Ready to scan Box label..."); toast.success("Ready to scan Box label...");
}} }}
className={cn( className={cn(
"absolute right-2 p-2 rounded-lg transition-all", "absolute right-2 p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 rounded-lg transition-all",
fieldScanning?.active ? "bg-primary text-white animate-pulse" : "text-muted hover:bg-slate-800" fieldScanning?.active ? "bg-primary text-white animate-pulse" : "text-muted hover:bg-slate-800"
)} )}
> >
@@ -524,7 +524,7 @@ export default function InventoryPage() {
key={t.id} key={t.id}
onClick={() => setAdjustType(t.id as any)} onClick={() => setAdjustType(t.id as any)}
className={cn( className={cn(
"flex-1 flex flex-col items-center py-3 rounded-xl transition-all", "flex-1 flex flex-col items-center py-3 lg:py-4 xl:py-5 rounded-xl transition-all",
adjustType === t.id ? "bg-slate-800 shadow-lg" : "text-muted" adjustType === t.id ? "bg-slate-800 shadow-lg" : "text-muted"
)} )}
> >
@@ -552,11 +552,11 @@ export default function InventoryPage() {
</div> </div>
{adjustType === 'TRASH' && ( {adjustType === 'TRASH' && (
<div className="w-full bg-red-500/5 border border-red-500/20 p-4 rounded-2xl"> <div className="w-full bg-red-500/5 border border-red-500/20 p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 rounded-2xl">
<select <select
value={trashReason} value={trashReason}
onChange={(e) => setTrashReason(e.target.value)} onChange={(e) => setTrashReason(e.target.value)}
className="w-full bg-background border border-slate-800 rounded-xl py-3 px-4 text-sm outline-none text-foreground" className="w-full bg-background border border-slate-800 rounded-xl py-3 lg:py-4 xl:py-5 px-4 lg:px-5 xl:px-6 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl outline-none text-foreground"
> >
<option>Damaged</option> <option>Damaged</option>
<option>Expired</option> <option>Expired</option>
@@ -608,7 +608,7 @@ export default function InventoryPage() {
type="text" type="text"
value={catEditedName} value={catEditedName}
onChange={e => setCatEditedName(e.target.value)} onChange={e => setCatEditedName(e.target.value)}
className="w-full bg-background border border-slate-800 rounded-xl py-3 px-4 text-sm outline-none text-secondary" className="w-full bg-background border border-slate-800 rounded-xl py-3 lg:py-4 xl:py-5 px-4 lg:px-5 xl:px-6 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl outline-none text-secondary"
/> />
</div> </div>
<div> <div>
@@ -616,7 +616,7 @@ export default function InventoryPage() {
<textarea <textarea
value={catEditedDesc} value={catEditedDesc}
onChange={e => setCatEditedDesc(e.target.value)} onChange={e => setCatEditedDesc(e.target.value)}
className="w-full bg-background border border-slate-800 rounded-xl py-3 px-4 text-sm outline-none text-secondary h-24 resize-none" className="w-full bg-background border border-slate-800 rounded-xl py-3 lg:py-4 xl:py-5 px-4 lg:px-5 xl:px-6 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl outline-none text-secondary h-24 resize-none"
/> />
</div> </div>
</div> </div>
@@ -641,7 +641,7 @@ export default function InventoryPage() {
{/* Box Manager Modal */} {/* Box Manager Modal */}
{showBoxManager && ( {showBoxManager && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-background/90 animate-in fade-in duration-300"> <div className="fixed inset-0 z-50 flex items-center justify-center p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 bg-background/90 animate-in fade-in duration-300">
<div className="w-full max-w-2xl bg-surface border border-slate-800 rounded-[2.5rem] shadow-2xl overflow-hidden flex flex-col max-h-[90vh]"> <div className="w-full max-w-2xl bg-surface border border-slate-800 rounded-[2.5rem] shadow-2xl overflow-hidden flex flex-col max-h-[90vh]">
<div className="p-4 md:p-6 pb-3 md:pb-4 flex flex-col gap-3 md:gap-4 shrink-0 bg-surface/70"> <div className="p-4 md:p-6 pb-3 md:pb-4 flex flex-col gap-3 md:gap-4 shrink-0 bg-surface/70">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
@@ -652,7 +652,7 @@ export default function InventoryPage() {
</h3> </h3>
<p className="text-sm text-muted font-normal mt-1">Manage physical box labels & printing</p> <p className="text-sm text-muted font-normal mt-1">Manage physical box labels & printing</p>
</div> </div>
<button onClick={() => { setShowBoxManager(false); setBoxSearchQuery(''); }} className="p-3 hover:bg-slate-800 rounded-full transition-colors text-secondary"> <button onClick={() => { setShowBoxManager(false); setBoxSearchQuery(''); }} className="p-3 lg:p-4 xl:p-5 lg:p-6 xl:p-8 hover:bg-slate-800 rounded-full transition-colors text-secondary">
<X size={24} /> <X size={24} />
</button> </button>
</div> </div>
@@ -663,7 +663,7 @@ export default function InventoryPage() {
placeholder="Search boxes..." placeholder="Search boxes..."
value={boxSearchQuery} value={boxSearchQuery}
onChange={(e) => setBoxSearchQuery(e.target.value)} onChange={(e) => setBoxSearchQuery(e.target.value)}
className="w-full bg-background border border-slate-800 rounded-2xl py-3.5 pl-11 pr-4 text-sm focus:border-primary outline-none transition-all placeholder:text-secondary" className="w-full bg-background border border-slate-800 rounded-2xl py-3 lg:py-4 xl:py-5.5 pl-11 pr-4 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl focus:border-primary outline-none transition-all placeholder:text-secondary"
/> />
<Search className="absolute left-4 top-1/2 -translate-y-1/2 text-secondary" size={18} /> <Search className="absolute left-4 top-1/2 -translate-y-1/2 text-secondary" size={18} />
{boxSearchQuery && ( {boxSearchQuery && (

View File

@@ -101,7 +101,7 @@ export default function LoginPage() {
key={user.id} key={user.id}
data-testid="user-list-item" data-testid="user-list-item"
onClick={() => handleSelectUser(user)} onClick={() => handleSelectUser(user)}
className="bg-slate-800/50 hover:bg-slate-800 border border-slate-800 hover:border-primary/40 p-4 rounded-2xl text-left transition-all group flex items-center justify-between" className="bg-slate-800/50 hover:bg-slate-800 border border-slate-800 hover:border-primary/40 p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 rounded-2xl text-left transition-all group flex items-center justify-between"
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-surface border border-slate-700 flex items-center justify-center text-muted group-hover:text-primary transition-colors"> <div className="w-8 h-8 rounded-full bg-surface border border-slate-700 flex items-center justify-center text-muted group-hover:text-primary transition-colors">
@@ -196,7 +196,7 @@ export default function LoginPage() {
</div> </div>
) : ( ) : (
<div className="space-y-4 animate-in slide-in-from-right-4 duration-300"> <div className="space-y-4 animate-in slide-in-from-right-4 duration-300">
<div className="bg-slate-800/30 p-4 rounded-2xl border border-slate-800 flex items-center gap-3"> <div className="bg-slate-800/30 p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 rounded-2xl border border-slate-800 flex items-center gap-3">
<button <button
onClick={() => setSelectedUserForLogin(null)} onClick={() => setSelectedUserForLogin(null)}
className="p-1 hover:bg-slate-700 rounded-lg transition-colors" className="p-1 hover:bg-slate-700 rounded-lg transition-colors"

View File

@@ -90,7 +90,7 @@ export default function LogsPage() {
<main className="p-3 md:p-8 max-w-7xl mx-auto space-y-3 md:space-y-6 mb-20"> <main className="p-3 md:p-8 max-w-7xl mx-auto space-y-3 md:space-y-6 mb-20">
<header className="flex flex-col sm:flex-row sm:items-end justify-between gap-3 md:gap-4"> <header className="flex flex-col sm:flex-row sm:items-end justify-between gap-3 md:gap-4">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="p-3 md:p-4 bg-primary/10 rounded-2xl text-primary border border-primary/20 shadow-xl shadow-primary/5"> <div className="p-3 md:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 bg-primary/10 rounded-2xl text-primary border border-primary/20 shadow-xl shadow-primary/5">
<History size={28} className="md:w-8 md:h-8" /> <History size={28} className="md:w-8 md:h-8" />
</div> </div>
<div> <div>
@@ -142,7 +142,7 @@ export default function LogsPage() {
placeholder="Filter by user, action or asset..." placeholder="Filter by user, action or asset..."
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
className="w-full bg-surface/50 border border-slate-800 rounded-2xl py-3.5 pr-4 pl-12 text-sm focus:border-primary outline-none transition-all placeholder:text-secondary shadow-2xl" className="w-full bg-surface/50 border border-slate-800 rounded-2xl py-3 lg:py-4 xl:py-5.5 pr-4 pl-12 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl focus:border-primary outline-none transition-all placeholder:text-secondary shadow-2xl"
/> />
<div className="absolute left-4 top-1/2 -translate-y-1/2 text-secondary transition-colors group-focus-within:text-primary"> <div className="absolute left-4 top-1/2 -translate-y-1/2 text-secondary transition-colors group-focus-within:text-primary">
<Search size={18} /> <Search size={18} />

View File

@@ -414,7 +414,7 @@ export default function Home() {
onClick={handleSync} onClick={handleSync}
disabled={syncing} disabled={syncing}
data-testid="manual-sync-button" data-testid="manual-sync-button"
className="p-2.5 bg-surface border border-slate-800 text-secondary rounded-xl hover:text-white transition-all active:scale-95 disabled:opacity-50" className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10.5 bg-surface border border-slate-800 text-secondary rounded-xl hover:text-white transition-all active:scale-95 disabled:opacity-50"
> >
<RefreshCw size={18} className={syncing ? "animate-spin text-primary" : ""} /> <RefreshCw size={18} className={syncing ? "animate-spin text-primary" : ""} />
</button> </button>
@@ -507,7 +507,7 @@ export default function Home() {
</h3> </h3>
<p className="text-xs text-secondary font-normal mt-1">Select the item you want to {mode.replace('_', ' ').toLowerCase()}</p> <p className="text-xs text-secondary font-normal mt-1">Select the item you want to {mode.replace('_', ' ').toLowerCase()}</p>
</div> </div>
<button onClick={() => setBoxMatches([])} className="p-2 hover:bg-slate-800 rounded-full"> <button onClick={() => setBoxMatches([])} className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 hover:bg-slate-800 rounded-full">
<X size={20} /> <X size={20} />
</button> </button>
</div> </div>
@@ -539,7 +539,7 @@ export default function Home() {
<footer className="mt-12 md:mt-20 mb-4 md:mb-8 flex flex-col items-center gap-2 opacity-70"> <footer className="mt-12 md:mt-20 mb-4 md:mb-8 flex flex-col items-center gap-2 opacity-70">
<p className="text-sm font-normal text-secondary">Powered by TFM Group Software</p> <p className="text-sm font-normal text-secondary">Powered by TFM Group Software</p>
<div className="h-px w-16 bg-slate-700/60" /> <div className="h-px w-16 bg-slate-700/60" />
<p className="text-xs font-mono text-secondary">v{versionData.version} {versionData.last_build} BUILD: dev-{(versionData as any).commit || 'N/A'}</p> <p className="text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl font-mono text-secondary">v{versionData.version} {versionData.last_build} BUILD: dev-{(versionData as any).commit || 'N/A'}</p>
</footer> </footer>
</div> </div>
</PageShell> </PageShell>

View File

@@ -58,10 +58,10 @@ export default function AIOnboarding({ onCancel, onComplete, categories, invento
}; };
return ( return (
<div data-testid="ai-extraction-overlay" className="fixed inset-0 z-50 bg-background flex flex-col p-6 animate-in fade-in slide-in-from-bottom-5 duration-300"> <div data-testid="ai-extraction-overlay" className="fixed inset-0 z-50 bg-background flex flex-col p-6 lg:p-8 xl:p-10 animate-in fade-in slide-in-from-bottom-5 duration-300">
<div className="flex justify-between items-center mb-6 shrink-0"> <div className="flex justify-between items-center mb-6 shrink-0">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="p-2 bg-primary/20 rounded-xl"> <div className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 bg-primary/20 rounded-xl">
<Sparkles className="text-primary w-5 h-5" /> <Sparkles className="text-primary w-5 h-5" />
</div> </div>
<div> <div>
@@ -72,7 +72,7 @@ export default function AIOnboarding({ onCancel, onComplete, categories, invento
<button <button
onClick={() => { stopLiveCamera(); onCancel(); }} onClick={() => { stopLiveCamera(); onCancel(); }}
aria-label="Close AI Discovery" aria-label="Close AI Discovery"
className="p-2 hover:bg-surface rounded-full cursor-pointer transition-colors focus:ring-2 focus:ring-blue-500 focus:outline-none" className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 hover:bg-surface rounded-full cursor-pointer transition-colors focus:ring-2 focus:ring-blue-500 focus:outline-none"
> >
<X size={24} /> <X size={24} />
</button> </button>
@@ -87,7 +87,7 @@ export default function AIOnboarding({ onCancel, onComplete, categories, invento
className={`flex-1 flex items-center justify-center gap-2 py-3 rounded-xl font-normal cursor-pointer transition-all focus:ring-2 focus:ring-primary focus:outline-none ${mode === 'item' ? 'bg-primary text-white shadow-lg' : 'text-muted hover:text-secondary'}`} className={`flex-1 flex items-center justify-center gap-2 py-3 rounded-xl font-normal cursor-pointer transition-all focus:ring-2 focus:ring-primary focus:outline-none ${mode === 'item' ? 'bg-primary text-white shadow-lg' : 'text-muted hover:text-secondary'}`}
> >
<Package size={18} /> <Package size={18} />
<span className="text-xs">Discovery Mode</span> <span className="text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl">Discovery Mode</span>
</button> </button>
<button <button
onClick={() => setMode('box')} onClick={() => setMode('box')}
@@ -95,11 +95,11 @@ export default function AIOnboarding({ onCancel, onComplete, categories, invento
className={`flex-1 flex items-center justify-center gap-2 py-3 rounded-xl font-normal cursor-pointer transition-all focus:ring-2 focus:ring-primary focus:outline-none ${mode === 'box' ? 'bg-primary text-white shadow-lg' : 'text-muted hover:text-secondary'}`} className={`flex-1 flex items-center justify-center gap-2 py-3 rounded-xl font-normal cursor-pointer transition-all focus:ring-2 focus:ring-primary focus:outline-none ${mode === 'box' ? 'bg-primary text-white shadow-lg' : 'text-muted hover:text-secondary'}`}
> >
<Layers size={18} /> <Layers size={18} />
<span className="text-xs">Box Lookup</span> <span className="text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl">Box Lookup</span>
</button> </button>
</div> </div>
<div className="flex-1 flex flex-col items-center justify-center border-2 border-dashed border-slate-800 rounded-[2.5rem] bg-surface/30 overflow-hidden px-4"> <div className="flex-1 flex flex-col items-center justify-center border-2 border-dashed border-slate-800 rounded-[2.5rem] bg-surface/30 overflow-hidden px-4 lg:px-5 xl:px-6">
<div className="w-20 h-20 bg-surface rounded-3xl flex items-center justify-center mb-6 shadow-inner text-primary"> <div className="w-20 h-20 bg-surface rounded-3xl flex items-center justify-center mb-6 shadow-inner text-primary">
<Camera size={32} /> <Camera size={32} />
</div> </div>
@@ -120,7 +120,7 @@ export default function AIOnboarding({ onCancel, onComplete, categories, invento
className="flex flex-col items-center justify-center gap-2 bg-primary text-white rounded-3xl font-normal shadow-2xl shadow-primary/20 cursor-pointer active:scale-95 transition-all focus:ring-2 focus:ring-blue-400 focus:outline-none" className="flex flex-col items-center justify-center gap-2 bg-primary text-white rounded-3xl font-normal shadow-2xl shadow-primary/20 cursor-pointer active:scale-95 transition-all focus:ring-2 focus:ring-blue-400 focus:outline-none"
> >
<Camera size={24} /> <Camera size={24} />
<span className="text-sm">Scan Camera</span> <span className="text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl">Scan Camera</span>
</button> </button>
<button <button
onClick={() => fileInputRef.current?.click()} onClick={() => fileInputRef.current?.click()}
@@ -129,7 +129,7 @@ export default function AIOnboarding({ onCancel, onComplete, categories, invento
className="flex flex-col items-center justify-center gap-2 bg-surface text-secondary border border-slate-800 rounded-3xl font-normal cursor-pointer active:scale-95 transition-all focus:ring-2 focus:ring-blue-500 focus:outline-none" className="flex flex-col items-center justify-center gap-2 bg-surface text-secondary border border-slate-800 rounded-3xl font-normal cursor-pointer active:scale-95 transition-all focus:ring-2 focus:ring-blue-500 focus:outline-none"
> >
<ImageIcon size={24} /> <ImageIcon size={24} />
<span className="text-sm">Upload Photo</span> <span className="text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl">Upload Photo</span>
</button> </button>
</div> </div>
@@ -339,7 +339,7 @@ export default function AIOnboarding({ onCancel, onComplete, categories, invento
<textarea <textarea
value={extractedItems[editingIndex].Description || extractedItems[editingIndex].description || ''} value={extractedItems[editingIndex].Description || extractedItems[editingIndex].description || ''}
onChange={(e) => updateEditingItem({ Description: e.target.value })} onChange={(e) => updateEditingItem({ Description: e.target.value })}
className="bg-transparent w-full text-sm leading-tight outline-none resize-none h-8 text-secondary py-0" className="bg-transparent w-full text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl leading-tight outline-none resize-none h-8 text-secondary py-0"
placeholder="Product description..." placeholder="Product description..."
/> />
</div> </div>
@@ -422,7 +422,7 @@ export default function AIOnboarding({ onCancel, onComplete, categories, invento
<div <div
key={idx} key={idx}
data-testid="extraction-item-row" data-testid="extraction-item-row"
className="bg-surface/70 border border-slate-800 rounded-3xl p-5 flex items-center justify-between group hover:border-primary/40 transition-all active:scale-[0.98] focus-within:border-primary/60 focus-within:ring-2 focus-within:ring-primary/20" className="bg-surface/70 border border-slate-800 rounded-3xl p-5 lg:p-6 xl:p-8 flex items-center justify-between group hover:border-primary/40 transition-all active:scale-[0.98] focus-within:border-primary/60 focus-within:ring-2 focus-within:ring-primary/20"
> >
<div <div
className="flex-1 min-w-0 cursor-pointer" className="flex-1 min-w-0 cursor-pointer"
@@ -461,14 +461,14 @@ export default function AIOnboarding({ onCancel, onComplete, categories, invento
setExtractedItems(newItems); setExtractedItems(newItems);
}} }}
aria-label={`Delete item: ${item.Item || item.name}`} aria-label={`Delete item: ${item.Item || item.name}`}
className="p-3 text-secondary hover:text-red-500 cursor-pointer transition-colors focus:ring-2 focus:ring-red-500 focus:outline-none rounded-lg" className="p-3 lg:p-4 xl:p-5 lg:p-6 xl:p-8 text-secondary hover:text-red-500 cursor-pointer transition-colors focus:ring-2 focus:ring-red-500 focus:outline-none rounded-lg"
> >
<X size={20} /> <X size={20} />
</button> </button>
<button <button
onClick={() => setEditingIndex(idx)} onClick={() => setEditingIndex(idx)}
aria-label={`Edit item: ${item.Item || item.name}`} aria-label={`Edit item: ${item.Item || item.name}`}
className="p-3 bg-primary/10 text-primary rounded-2xl cursor-pointer hover:bg-primary/20 transition-all focus:ring-2 focus:ring-primary focus:outline-none" className="p-3 lg:p-4 xl:p-5 lg:p-6 xl:p-8 bg-primary/10 text-primary rounded-2xl cursor-pointer hover:bg-primary/20 transition-all focus:ring-2 focus:ring-primary focus:outline-none"
> >
<ChevronDown size={20} className="-rotate-90" /> <ChevronDown size={20} className="-rotate-90" />
</button> </button>

View File

@@ -117,7 +117,7 @@ export default function AdminOverlay({
<div className="absolute inset-y-0 right-0 w-full max-w-md sm:max-w-lg md:max-w-md bg-surface border-l border-slate-800 shadow-2xl flex flex-col animate-in slide-in-from-right duration-500"> <div className="absolute inset-y-0 right-0 w-full max-w-md sm:max-w-lg md:max-w-md bg-surface border-l border-slate-800 shadow-2xl flex flex-col animate-in slide-in-from-right duration-500">
<div className="p-3 md:p-6 border-b border-slate-800 flex items-center justify-between"> <div className="p-3 md:p-6 border-b border-slate-800 flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="p-2 bg-slate-800 rounded-xl text-primary"> <div className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 bg-slate-800 rounded-xl text-primary">
<Shield size={20} /> <Shield size={20} />
</div> </div>
<h2 className="text-xl font-normal text-white">System Admin</h2> <h2 className="text-xl font-normal text-white">System Admin</h2>
@@ -125,7 +125,7 @@ export default function AdminOverlay({
<button <button
data-testid="close-admin-overlay" data-testid="close-admin-overlay"
onClick={onClose} onClick={onClose}
className="p-2 hover:bg-slate-800 rounded-full transition-colors text-muted focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none" className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 hover:bg-slate-800 rounded-full transition-colors text-muted focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none"
aria-label="Close" aria-label="Close"
> >
<X size={20} /> <X size={20} />
@@ -171,7 +171,7 @@ export default function AdminOverlay({
loading: false, loading: false,
}); });
}} }}
className="p-2 text-rose-500 hover:bg-rose-500/10 rounded-lg transition-all focus-visible:ring-2 focus-visible:ring-rose-500 focus-visible:outline-none" className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 text-rose-500 hover:bg-rose-500/10 rounded-lg transition-all focus-visible:ring-2 focus-visible:ring-rose-500 focus-visible:outline-none"
aria-label={`Delete user ${u.username}`} aria-label={`Delete user ${u.username}`}
> >
<Trash2 size={16} /> <Trash2 size={16} />
@@ -220,7 +220,7 @@ export default function AdminOverlay({
loading: false, loading: false,
}); });
}} }}
className="p-2 text-rose-500 hover:bg-rose-500/10 rounded-lg transition-all focus-visible:ring-2 focus-visible:ring-rose-500 focus-visible:outline-none" className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 text-rose-500 hover:bg-rose-500/10 rounded-lg transition-all focus-visible:ring-2 focus-visible:ring-rose-500 focus-visible:outline-none"
aria-label={`Delete category ${cat.name}`} aria-label={`Delete category ${cat.name}`}
> >
<Trash2 size={16} /> <Trash2 size={16} />
@@ -235,7 +235,7 @@ export default function AdminOverlay({
<AlertTriangle size={16} /> <AlertTriangle size={16} />
<p className="text-sm font-normal">Sign Out</p> <p className="text-sm font-normal">Sign Out</p>
</div> </div>
<p className="text-xs text-muted">End your session and return to the login screen.</p> <p className="text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl text-muted">End your session and return to the login screen.</p>
<button <button
onClick={() => { onClick={() => {
import('@/lib/auth').then(m => m.clearAuth()); import('@/lib/auth').then(m => m.clearAuth());

View File

@@ -25,7 +25,7 @@ export default function BottomNav({
const isAdmin = pathname === '/admin'; const isAdmin = pathname === '/admin';
return ( return (
<footer className="fixed bottom-0 left-0 right-0 py-3 px-2 pb-safe bg-background border-t border-slate-900 z-40 transition-all duration-300"> <footer className="fixed bottom-0 left-0 right-0 py-3 lg:py-4 xl:py-5 px-2 pb-safe bg-background border-t border-slate-900 z-40 transition-all duration-300">
<div className="max-w-xl mx-auto flex justify-around items-center text-secondary"> <div className="max-w-xl mx-auto flex justify-around items-center text-secondary">
<button <button

View File

@@ -44,7 +44,7 @@ export default function CameraView({
return ( return (
<div className="w-full max-w-md mx-auto flex flex-col gap-3 md:gap-4"> <div className="w-full max-w-md mx-auto flex flex-col gap-3 md:gap-4">
{/* Video Viewport Area */} {/* Video Viewport Area */}
<div className="relative w-full aspect-square overflow-hidden rounded-[2.5rem] shadow-[0_20px_50px_rgba(0,0,0,0.5)] bg-background border-2 border-slate-800/50 p-4 sm:p-6"> <div className="relative w-full aspect-square overflow-hidden rounded-[2.5rem] shadow-[0_20px_50px_rgba(0,0,0,0.5)] bg-background border-2 border-slate-800/50 p-4 sm:p-6 lg:p-8 xl:p-10">
<div className="absolute inset-4 sm:inset-6 z-10 pointer-events-none flex items-center justify-center"> <div className="absolute inset-4 sm:inset-6 z-10 pointer-events-none flex items-center justify-center">
<div className="w-full h-full border border-primary/30 rounded-[2rem] relative"> <div className="w-full h-full border border-primary/30 rounded-[2rem] relative">
<div className="absolute top-0 left-0 w-10 h-10 border-t-4 border-l-4 border-primary rounded-tl-2xl shadow-[0_0_15px_rgba(59,130,246,0.5)]" /> <div className="absolute top-0 left-0 w-10 h-10 border-t-4 border-l-4 border-primary rounded-tl-2xl shadow-[0_0_15px_rgba(59,130,246,0.5)]" />
@@ -89,7 +89,7 @@ export default function CameraView({
</div> </div>
</div> </div>
</div> </div>
<div className="p-6 bg-surface/90 border-t border-slate-800 flex flex-col gap-4"> <div className="p-6 lg:p-8 xl:p-10 bg-surface/90 border-t border-slate-800 flex flex-col gap-4">
<div className="flex flex-col items-center gap-1"> <div className="flex flex-col items-center gap-1">
<p className="text-sm font-normal text-white italic text-center">Text Found</p> <p className="text-sm font-normal text-white italic text-center">Text Found</p>
<p className="text-xs text-muted font-normal text-center">Tap any text to use it</p> <p className="text-xs text-muted font-normal text-center">Tap any text to use it</p>
@@ -99,7 +99,7 @@ export default function CameraView({
aria-label="Cancel text selection" aria-label="Cancel text selection"
className="w-full py-4 bg-slate-800 hover:bg-slate-700 text-white rounded-2xl font-normal text-xs cursor-pointer transition-all active:scale-95 border border-slate-700 focus:ring-2 focus:ring-blue-500 focus:outline-none" className="w-full py-4 bg-slate-800 hover:bg-slate-700 text-white rounded-2xl font-normal text-xs cursor-pointer transition-all active:scale-95 border border-slate-700 focus:ring-2 focus:ring-blue-500 focus:outline-none"
> >
Cancel Close OCR Preview
</button> </button>
</div> </div>
</div> </div>
@@ -131,7 +131,7 @@ export default function CameraView({
</div> </div>
{/* External Controls Area */} {/* External Controls Area */}
<div className="flex flex-col gap-4 bg-surface/50 p-5 rounded-[2.5rem] border border-slate-800/50 shadow-2xl"> <div className="flex flex-col gap-4 bg-surface/50 p-5 lg:p-6 xl:p-8 rounded-[2.5rem] border border-slate-800/50 shadow-2xl">
<div className="flex items-center gap-3 w-full"> <div className="flex items-center gap-3 w-full">
{hasZoom && ( {hasZoom && (
<button <button
@@ -153,7 +153,7 @@ export default function CameraView({
</button> </button>
)} )}
<div className="flex-1 h-14 bg-background/40 border border-slate-800/50 rounded-2xl flex items-center justify-center gap-3 px-4 relative overflow-hidden group"> <div className="flex-1 h-14 bg-background/40 border border-slate-800/50 rounded-2xl flex items-center justify-center gap-3 px-4 lg:px-5 xl:px-6 relative overflow-hidden group">
{ocrProcessing ? ( {ocrProcessing ? (
<> <>
<RefreshCw className="animate-spin text-primary" size={18} /> <RefreshCw className="animate-spin text-primary" size={18} />

View File

@@ -52,9 +52,9 @@ export default function CategoryCreationModal({
return ( return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40"> <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
<div className="bg-surface border border-slate-800 rounded-lg shadow-xl max-w-sm w-full mx-4"> <div className="bg-surface border border-slate-800 rounded-lg shadow-xl max-w-sm w-full mx-4">
<div className="flex items-center justify-between p-6 border-b border-slate-800"> <div className="flex items-center justify-between p-6 lg:p-8 xl:p-10 border-b border-slate-800">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="p-2 bg-primary/10 rounded-lg text-primary"> <div className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 bg-primary/10 rounded-lg text-primary">
<Tag size={20} /> <Tag size={20} />
</div> </div>
<h2 className="text-lg font-normal text-white">New Category</h2> <h2 className="text-lg font-normal text-white">New Category</h2>
@@ -69,7 +69,7 @@ export default function CategoryCreationModal({
</button> </button>
</div> </div>
<form onSubmit={handleSubmit} className="p-6 space-y-4"> <form onSubmit={handleSubmit} className="p-6 lg:p-8 xl:p-10 space-y-4">
<div> <div>
<label className="text-sm font-normal text-secondary">Name</label> <label className="text-sm font-normal text-secondary">Name</label>
<input <input
@@ -80,7 +80,7 @@ export default function CategoryCreationModal({
setError(null); setError(null);
}} }}
placeholder="e.g., Electronics" placeholder="e.g., Electronics"
className="w-full mt-2 px-3 py-2.5 bg-slate-800 border border-slate-700 rounded text-white placeholder:text-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-primary" className="w-full mt-2 px-3 lg:px-4 xl:px-5 py-2 lg:py-3 xl:py-4.5 bg-slate-800 border border-slate-700 rounded text-white placeholder:text-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-primary"
disabled={loading} disabled={loading}
autoFocus autoFocus
/> />
@@ -93,14 +93,14 @@ export default function CategoryCreationModal({
value={description} value={description}
onChange={(e) => setDescription(e.target.value)} onChange={(e) => setDescription(e.target.value)}
placeholder="Brief category description" placeholder="Brief category description"
className="w-full mt-2 px-3 py-2.5 bg-slate-800 border border-slate-700 rounded text-white placeholder:text-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-primary" className="w-full mt-2 px-3 lg:px-4 xl:px-5 py-2 lg:py-3 xl:py-4.5 bg-slate-800 border border-slate-700 rounded text-white placeholder:text-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-primary"
disabled={loading} disabled={loading}
/> />
</div> </div>
{error && ( {error && (
<div className="p-3 bg-rose-500/10 border border-rose-500/20 rounded"> <div className="p-3 lg:p-4 xl:p-5 lg:p-6 xl:p-8 bg-rose-500/10 border border-rose-500/20 rounded">
<p className="text-sm text-rose-400">{error}</p> <p className="text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl text-rose-400">{error}</p>
</div> </div>
)} )}

View File

@@ -85,7 +85,7 @@ export default function CreateUserModal({ show, onClose, onUserCreated }: Create
</div> </div>
{/* Form */} {/* Form */}
<form onSubmit={handleSubmit} className="p-6 space-y-4"> <form onSubmit={handleSubmit} className="p-6 lg:p-8 xl:p-10 space-y-4">
{/* Username Field */} {/* Username Field */}
<div> <div>
<label htmlFor="username" className="block text-sm font-normal text-secondary mb-2"> <label htmlFor="username" className="block text-sm font-normal text-secondary mb-2">
@@ -101,13 +101,13 @@ export default function CreateUserModal({ show, onClose, onUserCreated }: Create
if (errors.username) setErrors({ ...errors, username: undefined }); if (errors.username) setErrors({ ...errors, username: undefined });
}} }}
placeholder="Enter username" placeholder="Enter username"
className={`w-full px-3 py-2.5 bg-slate-800 border rounded text-white placeholder:text-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-primary ${ className={`w-full px-3 lg:px-4 xl:px-5 py-2 lg:py-3 xl:py-4.5 bg-slate-800 border rounded text-white placeholder:text-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-primary ${
errors.username ? 'border-red-500' : 'border-slate-700' errors.username ? 'border-red-500' : 'border-slate-700'
}`} }`}
disabled={loading} disabled={loading}
/> />
{errors.username && ( {errors.username && (
<p className="mt-1.5 text-sm text-red-500">{errors.username}</p> <p className="mt-1.5 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl text-red-500">{errors.username}</p>
)} )}
</div> </div>
@@ -126,13 +126,13 @@ export default function CreateUserModal({ show, onClose, onUserCreated }: Create
if (errors.password) setErrors({ ...errors, password: undefined }); if (errors.password) setErrors({ ...errors, password: undefined });
}} }}
placeholder="Enter password" placeholder="Enter password"
className={`w-full px-3 py-2.5 bg-slate-800 border rounded text-white placeholder:text-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-primary ${ className={`w-full px-3 lg:px-4 xl:px-5 py-2 lg:py-3 xl:py-4.5 bg-slate-800 border rounded text-white placeholder:text-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-primary ${
errors.password ? 'border-red-500' : 'border-slate-700' errors.password ? 'border-red-500' : 'border-slate-700'
}`} }`}
disabled={loading} disabled={loading}
/> />
{errors.password && ( {errors.password && (
<p className="mt-1.5 text-sm text-red-500">{errors.password}</p> <p className="mt-1.5 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl text-red-500">{errors.password}</p>
)} )}
</div> </div>

View File

@@ -15,7 +15,7 @@ export default function FilterBar({ searchQuery, onChange }: FilterBarProps) {
placeholder="Search catalog..." placeholder="Search catalog..."
value={searchQuery} value={searchQuery}
onChange={(e) => onChange(e.target.value)} onChange={(e) => onChange(e.target.value)}
className="w-full bg-surface border border-slate-800 rounded-2xl py-3.5 pr-4 pl-11 text-sm focus:border-primary outline-none transition-all placeholder:text-secondary" className="w-full bg-surface border border-slate-800 rounded-2xl py-3 lg:py-4 xl:py-5.5 pr-4 pl-11 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl focus:border-primary outline-none transition-all placeholder:text-secondary"
/> />
<div className="absolute left-4 top-1/2 -translate-y-1/2 text-secondary"> <div className="absolute left-4 top-1/2 -translate-y-1/2 text-secondary">
<Search size={18} /> <Search size={18} />

View File

@@ -79,7 +79,7 @@ const IdentityCheckOverlay = memo(({ show, users, onAuthenticated }: IdentityChe
key={user.id} key={user.id}
data-testid="user-list-item" data-testid="user-list-item"
onClick={() => handleSelectUser(user)} onClick={() => handleSelectUser(user)}
className="bg-slate-800/40 hover:bg-slate-800 border border-slate-800/50 hover:border-primary/40 p-5 rounded-[1.5rem] text-left transition-all group flex items-center justify-between shadow-sm active:scale-[0.98]" className="bg-slate-800/40 hover:bg-slate-800 border border-slate-800/50 hover:border-primary/40 p-5 lg:p-6 xl:p-8 rounded-[1.5rem] text-left transition-all group flex items-center justify-between shadow-sm active:scale-[0.98]"
> >
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="w-10 h-10 rounded-2xl bg-surface border border-slate-800 flex items-center justify-center text-muted group-hover:text-primary transition-all group-hover:scale-110"> <div className="w-10 h-10 rounded-2xl bg-surface border border-slate-800 flex items-center justify-center text-muted group-hover:text-primary transition-all group-hover:scale-110">
@@ -126,7 +126,7 @@ const IdentityCheckOverlay = memo(({ show, users, onAuthenticated }: IdentityChe
data-testid="username-input" data-testid="username-input"
type="text" type="text"
autoFocus autoFocus
className="w-full bg-background/50 border border-slate-800/80 focus:border-primary/50 focus:bg-background rounded-[1.25rem] py-4.5 pl-14 pr-5 text-sm text-secondary focus:outline-none transition-all placeholder:text-muted font-mono" className="w-full bg-background/50 border border-slate-800/80 focus:border-primary/50 focus:bg-background rounded-[1.25rem] py-4.5 pl-14 pr-5 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl text-secondary focus:outline-none transition-all placeholder:text-muted font-mono"
placeholder="DIRECTORY ID" placeholder="DIRECTORY ID"
/> />
</div> </div>
@@ -140,7 +140,7 @@ const IdentityCheckOverlay = memo(({ show, users, onAuthenticated }: IdentityChe
data-testid="password-input" data-testid="password-input"
type="password" type="password"
onKeyDown={(e) => e.key === 'Enter' && handleLogin()} onKeyDown={(e) => e.key === 'Enter' && handleLogin()}
className="w-full bg-background/50 border border-slate-800/80 focus:border-primary/50 focus:bg-background rounded-[1.25rem] py-4.5 pl-14 pr-5 text-sm text-secondary focus:outline-none transition-all placeholder:text-muted font-mono" className="w-full bg-background/50 border border-slate-800/80 focus:border-primary/50 focus:bg-background rounded-[1.25rem] py-4.5 pl-14 pr-5 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl text-secondary focus:outline-none transition-all placeholder:text-muted font-mono"
placeholder="••••••••" placeholder="••••••••"
/> />
</div> </div>
@@ -156,7 +156,7 @@ const IdentityCheckOverlay = memo(({ show, users, onAuthenticated }: IdentityChe
</div> </div>
) : ( ) : (
<div className="space-y-5 animate-in slide-in-from-bottom-5 duration-300"> <div className="space-y-5 animate-in slide-in-from-bottom-5 duration-300">
<div data-testid="user-display" className="bg-background/50 p-5 rounded-[1.5rem] border border-slate-800 flex items-center justify-between group"> <div data-testid="user-display" className="bg-background/50 p-5 lg:p-6 xl:p-8 rounded-[1.5rem] border border-slate-800 flex items-center justify-between group">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="w-10 h-10 rounded-2xl bg-primary/10 border border-primary/20 flex items-center justify-center text-primary shadow-lg"> <div className="w-10 h-10 rounded-2xl bg-primary/10 border border-primary/20 flex items-center justify-center text-primary shadow-lg">
<Shield size={20} /> <Shield size={20} />
@@ -168,7 +168,7 @@ const IdentityCheckOverlay = memo(({ show, users, onAuthenticated }: IdentityChe
</div> </div>
<button <button
onClick={() => setSelectedUserForLogin(null)} onClick={() => setSelectedUserForLogin(null)}
className="p-2 hover:bg-slate-800 rounded-xl transition-all text-secondary hover:text-rose-500" className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 hover:bg-slate-800 rounded-xl transition-all text-secondary hover:text-rose-500"
title="Change User" title="Change User"
> >
<X size={18} /> <X size={18} />
@@ -184,7 +184,7 @@ const IdentityCheckOverlay = memo(({ show, users, onAuthenticated }: IdentityChe
type="password" type="password"
autoFocus autoFocus
onKeyDown={(e) => e.key === 'Enter' && handleLogin()} onKeyDown={(e) => e.key === 'Enter' && handleLogin()}
className="w-full bg-background/50 border border-slate-800/80 focus:border-primary/50 focus:bg-background rounded-[1.25rem] py-4.5 pl-14 pr-5 text-sm text-secondary focus:outline-none transition-all placeholder:text-muted font-mono" className="w-full bg-background/50 border border-slate-800/80 focus:border-primary/50 focus:bg-background rounded-[1.25rem] py-4.5 pl-14 pr-5 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl text-secondary focus:outline-none transition-all placeholder:text-muted font-mono"
placeholder="KEY-PHRASE" placeholder="KEY-PHRASE"
/> />
</div> </div>

View File

@@ -53,7 +53,7 @@ export default function InventoryTable({
if (categories.length === 0) { if (categories.length === 0) {
return ( return (
<div className="py-20 text-center text-secondary"> <div className="py-2 lg:py-3 xl:py-40 text-center text-secondary">
<Package size={48} className="mx-auto mb-4 opacity-10" /> <Package size={48} className="mx-auto mb-4 opacity-10" />
<p className="font-normal">No results found</p> <p className="font-normal">No results found</p>
</div> </div>
@@ -67,7 +67,7 @@ export default function InventoryTable({
return ( return (
<div key={cat} className="bg-surface/50 border border-slate-800/50 rounded-3xl overflow-hidden transition-all duration-300"> <div key={cat} className="bg-surface/50 border border-slate-800/50 rounded-3xl overflow-hidden transition-all duration-300">
<div <div
className="w-full p-4 md:p-5 flex items-center justify-between hover:bg-surface/60 transition-colors cursor-pointer" className="w-full p-4 md:p-5 lg:p-6 xl:p-8 flex items-center justify-between hover:bg-surface/60 transition-colors cursor-pointer"
onClick={() => onExpandCategory(expandedCategory === cat ? null : cat)} onClick={() => onExpandCategory(expandedCategory === cat ? null : cat)}
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
@@ -75,7 +75,7 @@ export default function InventoryTable({
<Layers size={20} /> <Layers size={20} />
</div> </div>
<div className="text-left"> <div className="text-left">
<h3 className="card-title text-base sm:text-lg">{cat}</h3> <h3 className="card-title text-base sm:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl">{cat}</h3>
<p className="card-subtitle tracking-tight"> <p className="card-subtitle tracking-tight">
{categoryItems.length} Item types in stock {categoryItems.length} Item types in stock
</p> </p>
@@ -89,7 +89,7 @@ export default function InventoryTable({
e.stopPropagation(); e.stopPropagation();
onEditCategory(cat); onEditCategory(cat);
}} }}
className="p-2 hover:bg-slate-800 rounded-full text-muted hover:text-primary transition-colors relative z-10" className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 hover:bg-slate-800 rounded-full text-muted hover:text-primary transition-colors relative z-10"
> >
<EditIcon size={16} /> <EditIcon size={16} />
</button> </button>
@@ -98,7 +98,7 @@ export default function InventoryTable({
</div> </div>
{expandedCategory === cat && ( {expandedCategory === cat && (
<div className="p-4 pt-0 space-y-2 animate-in slide-in-from-top-4 duration-300"> <div className="p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 pt-0 space-y-2 animate-in slide-in-from-top-4 duration-300">
<div className="h-px bg-slate-800/50 mb-4 mx-2" /> <div className="h-px bg-slate-800/50 mb-4 mx-2" />
{categoryItems.map(item => ( {categoryItems.map(item => (
<div <div

View File

@@ -42,7 +42,7 @@ export default function ItemComparisonModal({
const hasChanges = fields.some(f => isDifferent(f.key)); const hasChanges = fields.some(f => isDifferent(f.key));
return ( return (
<div className="fixed inset-0 z-[200] flex items-center justify-center p-4 bg-background/80"> <div className="fixed inset-0 z-[200] flex items-center justify-center p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 bg-background/80">
<div className="bg-surface border border-slate-800 rounded-[2.5rem] p-8 w-full max-w-2xl shadow-2xl max-h-[90vh] overflow-y-auto"> <div className="bg-surface border border-slate-800 rounded-[2.5rem] p-8 w-full max-w-2xl shadow-2xl max-h-[90vh] overflow-y-auto">
<div className="flex items-center gap-3 mb-6"> <div className="flex items-center gap-3 mb-6">
<AlertTriangle size={24} className="text-amber-500" /> <AlertTriangle size={24} className="text-amber-500" />
@@ -68,7 +68,7 @@ export default function ItemComparisonModal({
return ( return (
<div <div
key={field.key} key={field.key}
className={`grid grid-cols-3 gap-4 p-3 rounded-lg ${ className={`grid grid-cols-3 gap-4 p-3 lg:p-4 xl:p-5 lg:p-6 xl:p-8 rounded-lg ${
different ? 'bg-amber-500/10 border border-amber-500/20' : 'bg-slate-800/30' different ? 'bg-amber-500/10 border border-amber-500/20' : 'bg-slate-800/30'
}`} }`}
> >
@@ -85,8 +85,8 @@ export default function ItemComparisonModal({
</div> </div>
{!hasChanges && ( {!hasChanges && (
<div className="p-4 bg-slate-800/50 rounded-xl mb-6 border border-slate-700"> <div className="p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 bg-slate-800/50 rounded-xl mb-6 border border-slate-700">
<p className="text-sm text-secondary"> Items are identical. No update needed.</p> <p className="text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl text-secondary"> Items are identical. No update needed.</p>
</div> </div>
)} )}

View File

@@ -14,7 +14,7 @@ export default function LogsOverlay({ show, onClose, logs, inventory }: LogsOver
if (!show) return null; if (!show) return null;
return ( return (
<div className="fixed inset-0 z-50 flex flex-col bg-background p-6 animate-in slide-in-from-bottom-20 duration-500"> <div className="fixed inset-0 z-50 flex flex-col bg-background p-6 lg:p-8 xl:p-10 animate-in slide-in-from-bottom-20 duration-500">
<div className="flex justify-between items-center mb-8"> <div className="flex justify-between items-center mb-8">
<div> <div>
<h2 className="text-2xl font-normal tracking-tight">Audit History</h2> <h2 className="text-2xl font-normal tracking-tight">Audit History</h2>
@@ -22,7 +22,7 @@ export default function LogsOverlay({ show, onClose, logs, inventory }: LogsOver
</div> </div>
<button <button
onClick={onClose} onClick={onClose}
className="p-3 bg-surface rounded-full text-secondary" className="p-3 lg:p-4 xl:p-5 lg:p-6 xl:p-8 bg-surface rounded-full text-secondary"
> >
<X size={24} /> <X size={24} />
</button> </button>
@@ -36,7 +36,7 @@ export default function LogsOverlay({ show, onClose, logs, inventory }: LogsOver
</div> </div>
) : ( ) : (
logs.map((log) => ( logs.map((log) => (
<div key={log.id} className="bg-surface/70 border border-slate-800 p-4 rounded-2xl flex items-center justify-between gap-4"> <div key={log.id} className="bg-surface/70 border border-slate-800 p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 rounded-2xl flex items-center justify-between gap-4">
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<p className={`text-xs font-normal ${ <p className={`text-xs font-normal ${
@@ -54,11 +54,11 @@ export default function LogsOverlay({ show, onClose, logs, inventory }: LogsOver
</div> </div>
{(log.details || log.timestamp) && ( {(log.details || log.timestamp) && (
<div className="flex items-center gap-3 mt-2"> <div className="flex items-center gap-3 mt-2">
<p className="text-xs text-secondary font-mono"> <p className="text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl text-secondary font-mono">
{new Date(log.timestamp).toLocaleString()} {new Date(log.timestamp).toLocaleString()}
</p> </p>
{log.details && ( {log.details && (
<span className="text-xs bg-slate-800 text-muted px-2 py-0.5 rounded border border-slate-700 font-mono"> <span className="text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl bg-slate-800 text-muted px-2 py-0.5 rounded border border-slate-700 font-mono">
{log.details} {log.details}
</span> </span>
)} )}

View File

@@ -25,7 +25,7 @@ export default function LogsTable({ logs, loading }: LogsTableProps) {
if (loading) { if (loading) {
return ( return (
<div className="flex flex-col items-center justify-center py-32 text-secondary gap-4 animate-pulse"> <div className="flex flex-col items-center justify-center py-3 lg:py-4 xl:py-52 text-secondary gap-4 animate-pulse">
<div className="w-10 h-10 border-4 border-primary/20 border-t-primary rounded-full animate-spin" /> <div className="w-10 h-10 border-4 border-primary/20 border-t-primary rounded-full animate-spin" />
<p className="text-xs font-normal tracking-widest italic">Securing Audit Stream...</p> <p className="text-xs font-normal tracking-widest italic">Securing Audit Stream...</p>
</div> </div>
@@ -53,7 +53,7 @@ export default function LogsTable({ logs, loading }: LogsTableProps) {
<button <button
key={log.id} key={log.id}
onClick={() => setSelectedLog(log)} onClick={() => setSelectedLog(log)}
className="w-full text-left bg-surface/50 border border-slate-800/30 p-3 px-4 rounded-2xl flex items-center justify-between gap-4 hover:bg-slate-800/40 hover:border-primary/30 transition-all group active:scale-[0.99] relative overflow-hidden shadow-sm" className="w-full text-left bg-surface/50 border border-slate-800/30 p-3 lg:p-4 xl:p-5 lg:p-6 xl:p-8 px-4 lg:px-5 xl:px-6 rounded-2xl flex items-center justify-between gap-4 hover:bg-slate-800/40 hover:border-primary/30 transition-all group active:scale-[0.99] relative overflow-hidden shadow-sm"
> >
<div className="flex-1 min-w-0 z-10 flex items-center gap-4"> <div className="flex-1 min-w-0 z-10 flex items-center gap-4">
{/* Compact Action Badge */} {/* Compact Action Badge */}
@@ -111,7 +111,7 @@ export default function LogsTable({ logs, loading }: LogsTableProps) {
{selectedLog.resolved_name} {selectedLog.resolved_name}
</h2> </h2>
</div> </div>
<button onClick={() => setSelectedLog(null)} className="p-3 bg-slate-800/50 hover:bg-slate-800 rounded-2xl text-muted transition-colors border border-slate-800 shrink-0 shadow-lg"> <button onClick={() => setSelectedLog(null)} className="p-3 lg:p-4 xl:p-5 lg:p-6 xl:p-8 bg-slate-800/50 hover:bg-slate-800 rounded-2xl text-muted transition-colors border border-slate-800 shrink-0 shadow-lg">
<X size={20} /> <X size={20} />
</button> </button>
</div> </div>

View File

@@ -28,12 +28,12 @@ export default function NewItemDialog({ onScannerClick, onAddItemClick }: NewIte
onClick={onAddItemClick} onClick={onAddItemClick}
className="w-full flex flex-col items-center justify-center p-4 md:p-6 rounded-[2rem] bg-indigo-500/5 border border-indigo-500/20 group hover:border-indigo-500/50 transition-all font-normal text-indigo-400 gap-2 md:gap-3" className="w-full flex flex-col items-center justify-center p-4 md:p-6 rounded-[2rem] bg-indigo-500/5 border border-indigo-500/20 group hover:border-indigo-500/50 transition-all font-normal text-indigo-400 gap-2 md:gap-3"
> >
<div className="p-4 bg-indigo-500/10 rounded-2xl group-hover:scale-110 transition-transform shadow-lg shadow-indigo-500/10"> <div className="p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 bg-indigo-500/10 rounded-2xl group-hover:scale-110 transition-transform shadow-lg shadow-indigo-500/10">
<Sparkles size={32} /> <Sparkles size={32} />
</div> </div>
<div className="text-center"> <div className="text-center">
<p className="text-lg leading-tight">Add New Item</p> <p className="text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl leading-tight">Add New Item</p>
<p className="text-xs opacity-60 font-mono mt-1">AI Smart Discovery</p> <p className="text-xs lg:text-sm xl:text-base lg:text-lg xl:text-xl lg:text-2xl xl:text-3xl lg:text-4xl xl:text-5xl opacity-60 font-mono mt-1">AI Smart Discovery</p>
</div> </div>
</button> </button>
</div> </div>

View File

@@ -54,14 +54,14 @@ export default function ScannerSection({
</div> </div>
{/* Scanner Section */} {/* Scanner Section */}
<section className="glass-card rounded-3xl p-6"> <section className="glass-card rounded-3xl p-6 lg:p-8 xl:p-10">
{showScanner ? ( {showScanner ? (
<div className="space-y-2 md:space-y-3"> <div className="space-y-2 md:space-y-3">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<h2 className="text-lg font-normal">scanning...</h2> <h2 className="text-lg font-normal">scanning...</h2>
<button <button
onClick={() => onShowScanner(false)} onClick={() => onShowScanner(false)}
className="p-2.5 bg-surface border border-slate-800 text-secondary rounded-xl hover:text-rose-500 transition-all active:scale-95" className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10.5 bg-surface border border-slate-800 text-secondary rounded-xl hover:text-rose-500 transition-all active:scale-95"
> >
<X size={18} /> <X size={18} />
</button> </button>

View File

@@ -9,7 +9,7 @@ interface StatCardProps {
export default function StatCard({ label, value, icon: Icon }: StatCardProps) { export default function StatCard({ label, value, icon: Icon }: StatCardProps) {
return ( return (
<div className="flex justify-between items-center gap-2 p-3.5 bg-surface/70 border border-slate-800/50 rounded-2xl shadow-sm transition-all hover:bg-surface" role="status"> <div className="flex justify-between items-center gap-2 p-3 lg:p-4 xl:p-5 lg:p-6 xl:p-8.5 bg-surface/70 border border-slate-800/50 rounded-2xl shadow-sm transition-all hover:bg-surface" role="status">
<div className="flex items-center gap-2.5 min-w-0"> <div className="flex items-center gap-2.5 min-w-0">
{Icon && <Icon className="w-5 h-5 text-primary flex-shrink-0" aria-hidden="true" />} {Icon && <Icon className="w-5 h-5 text-primary flex-shrink-0" aria-hidden="true" />}
<span className="text-base md:text-lg text-secondary font-normal truncate"> <span className="text-base md:text-lg text-secondary font-normal truncate">

View File

@@ -77,7 +77,7 @@ export default function StockAdjustmentPanel({
{!isEditing && ( {!isEditing && (
<button <button
onClick={() => onEdit(selectedItem)} onClick={() => onEdit(selectedItem)}
className="p-2 hover:bg-slate-800 rounded-full text-secondary" className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 hover:bg-slate-800 rounded-full text-secondary"
> >
<Edit2 size={20} /> <Edit2 size={20} />
</button> </button>
@@ -85,7 +85,7 @@ export default function StockAdjustmentPanel({
{isEditing && ( {isEditing && (
<button <button
onClick={onDeleteItem} onClick={onDeleteItem}
className="p-2 hover:bg-red-500/20 rounded-full text-red-500" className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 hover:bg-red-500/20 rounded-full text-red-500"
> >
<Trash2 size={20} /> <Trash2 size={20} />
</button> </button>
@@ -93,7 +93,7 @@ export default function StockAdjustmentPanel({
<button <button
onClick={onCancel} onClick={onCancel}
data-testid="adjustment-cancel" data-testid="adjustment-cancel"
className="p-2 hover:bg-slate-800 rounded-full" className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 hover:bg-slate-800 rounded-full"
> >
<X size={20} /> <X size={20} />
</button> </button>
@@ -119,7 +119,7 @@ export default function StockAdjustmentPanel({
type="text" type="text"
value={editedItem.part_number || ''} value={editedItem.part_number || ''}
onChange={e => onEditChange({ ...editedItem, part_number: e.target.value })} onChange={e => onEditChange({ ...editedItem, part_number: e.target.value })}
className="w-full bg-background border border-slate-800 rounded-xl py-3 px-4 text-sm font-mono outline-none text-secondary" className="w-full bg-background border border-slate-800 rounded-xl py-3 lg:py-4 xl:py-5 px-4 lg:px-5 xl:px-6 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl font-mono outline-none text-secondary"
placeholder="e.g. PN-12345" placeholder="e.g. PN-12345"
/> />
</div> </div>
@@ -131,7 +131,7 @@ export default function StockAdjustmentPanel({
list="existing-categories" list="existing-categories"
value={editedItem.category || ''} value={editedItem.category || ''}
onChange={e => onEditChange({ ...editedItem, category: e.target.value })} onChange={e => onEditChange({ ...editedItem, category: e.target.value })}
className="w-full bg-background border border-slate-800 rounded-xl py-3 px-4 text-sm outline-none text-secondary placeholder:text-muted" className="w-full bg-background border border-slate-800 rounded-xl py-3 lg:py-4 xl:py-5 px-4 lg:px-5 xl:px-6 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl outline-none text-secondary placeholder:text-muted"
placeholder="e.g. storage" placeholder="e.g. storage"
/> />
<datalist id="existing-categories"> <datalist id="existing-categories">
@@ -147,7 +147,7 @@ export default function StockAdjustmentPanel({
list="existing-types" list="existing-types"
value={editedItem.type || ''} value={editedItem.type || ''}
onChange={e => onEditChange({...editedItem, type: e.target.value})} onChange={e => onEditChange({...editedItem, type: e.target.value})}
className="w-full bg-background border border-slate-800 rounded-xl py-3 px-4 text-sm outline-none text-secondary" className="w-full bg-background border border-slate-800 rounded-xl py-3 lg:py-4 xl:py-5 px-4 lg:px-5 xl:px-6 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl outline-none text-secondary"
placeholder="e.g. spare parts" placeholder="e.g. spare parts"
/> />
</div> </div>
@@ -159,7 +159,7 @@ export default function StockAdjustmentPanel({
list="existing-boxes" list="existing-boxes"
value={editedItem.box_label || ''} value={editedItem.box_label || ''}
onChange={e => onEditChange({...editedItem, box_label: e.target.value})} onChange={e => onEditChange({...editedItem, box_label: e.target.value})}
className="w-full bg-background border border-slate-800 rounded-xl py-3 pl-4 pr-12 text-sm outline-none text-secondary placeholder:text-muted focus:border-primary transition-colors" className="w-full bg-background border border-slate-800 rounded-xl py-3 lg:py-4 xl:py-5 pl-4 pr-12 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl outline-none text-secondary placeholder:text-muted focus:border-primary transition-colors"
placeholder="e.g. SFPs 40G Cisco" placeholder="e.g. SFPs 40G Cisco"
/> />
<button <button
@@ -169,7 +169,7 @@ export default function StockAdjustmentPanel({
toast.success("Scanning for BOX label..."); toast.success("Scanning for BOX label...");
}} }}
className={cn( className={cn(
"absolute right-2 p-2 rounded-lg transition-all", "absolute right-2 p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 rounded-lg transition-all",
fieldScanning?.active ? "bg-primary text-white animate-pulse" : "text-muted hover:bg-slate-800" fieldScanning?.active ? "bg-primary text-white animate-pulse" : "text-muted hover:bg-slate-800"
)} )}
> >
@@ -183,7 +183,7 @@ export default function StockAdjustmentPanel({
type="text" type="text"
value={editedItem.connector || ''} value={editedItem.connector || ''}
onChange={e => onEditChange({...editedItem, connector: e.target.value})} onChange={e => onEditChange({...editedItem, connector: e.target.value})}
className="w-full bg-background border border-slate-800 rounded-xl py-3 px-4 text-sm outline-none text-secondary" className="w-full bg-background border border-slate-800 rounded-xl py-3 lg:py-4 xl:py-5 px-4 lg:px-5 xl:px-6 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl outline-none text-secondary"
placeholder="e.g. LC/UPC" placeholder="e.g. LC/UPC"
/> />
</div> </div>
@@ -193,7 +193,7 @@ export default function StockAdjustmentPanel({
type="text" type="text"
value={editedItem.size || ''} value={editedItem.size || ''}
onChange={e => onEditChange({...editedItem, size: e.target.value})} onChange={e => onEditChange({...editedItem, size: e.target.value})}
className="w-full bg-background border border-slate-800 rounded-xl py-3 px-4 text-sm outline-none text-secondary" className="w-full bg-background border border-slate-800 rounded-xl py-3 lg:py-4 xl:py-5 px-4 lg:px-5 xl:px-6 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl outline-none text-secondary"
placeholder="e.g. 5m / 1600GB" placeholder="e.g. 5m / 1600GB"
/> />
</div> </div>
@@ -203,7 +203,7 @@ export default function StockAdjustmentPanel({
type="text" type="text"
value={editedItem.color || ''} value={editedItem.color || ''}
onChange={e => onEditChange({...editedItem, color: e.target.value})} onChange={e => onEditChange({...editedItem, color: e.target.value})}
className="w-full bg-background border border-slate-800 rounded-xl py-3 px-4 text-sm outline-none text-secondary" className="w-full bg-background border border-slate-800 rounded-xl py-3 lg:py-4 xl:py-5 px-4 lg:px-5 xl:px-6 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl outline-none text-secondary"
placeholder="e.g. Black" placeholder="e.g. Black"
/> />
</div> </div>
@@ -212,7 +212,7 @@ export default function StockAdjustmentPanel({
<textarea <textarea
value={editedItem.description || ''} value={editedItem.description || ''}
onChange={e => onEditChange({ ...editedItem, description: e.target.value })} onChange={e => onEditChange({ ...editedItem, description: e.target.value })}
className="w-full bg-background border border-slate-800 rounded-xl py-3 px-4 text-sm outline-none text-secondary resize-none h-20" className="w-full bg-background border border-slate-800 rounded-xl py-3 lg:py-4 xl:py-5 px-4 lg:px-5 xl:px-6 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl outline-none text-secondary resize-none h-20"
placeholder="Item description..." placeholder="Item description..."
/> />
</div> </div>
@@ -239,7 +239,7 @@ export default function StockAdjustmentPanel({
key={t.id} key={t.id}
onClick={() => onTypeChange(t.id as 'ADD' | 'REMOVE' | 'TRASH')} onClick={() => onTypeChange(t.id as 'ADD' | 'REMOVE' | 'TRASH')}
className={cn( className={cn(
"flex-1 flex flex-col items-center py-3 rounded-xl transition-all", "flex-1 flex flex-col items-center py-3 lg:py-4 xl:py-5 rounded-xl transition-all",
adjustType === t.id ? "bg-slate-800 shadow-lg" : "text-muted" adjustType === t.id ? "bg-slate-800 shadow-lg" : "text-muted"
)} )}
> >
@@ -270,7 +270,7 @@ export default function StockAdjustmentPanel({
</div> </div>
{adjustType === 'TRASH' && ( {adjustType === 'TRASH' && (
<div className="w-full bg-red-500/5 border border-red-500/20 p-4 rounded-2xl animate-in shake duration-500"> <div className="w-full bg-red-500/5 border border-red-500/20 p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 rounded-2xl animate-in shake duration-500">
<div className="flex items-center gap-2 mb-3"> <div className="flex items-center gap-2 mb-3">
<AlertTriangle size={16} className="text-red-500" /> <AlertTriangle size={16} className="text-red-500" />
<span className="text-sm font-normal text-red-400">Waste Declaration</span> <span className="text-sm font-normal text-red-400">Waste Declaration</span>
@@ -278,7 +278,7 @@ export default function StockAdjustmentPanel({
<select <select
value={trashReason} value={trashReason}
onChange={(e) => onReasonChange(e.target.value)} onChange={(e) => onReasonChange(e.target.value)}
className="w-full bg-background border border-slate-800 rounded-xl py-3 px-4 text-sm outline-none text-secondary" className="w-full bg-background border border-slate-800 rounded-xl py-3 lg:py-4 xl:py-5 px-4 lg:px-5 xl:px-6 text-sm lg:text-base xl:text-lg lg:text-xl xl:text-2xl lg:text-3xl xl:text-4xl outline-none text-secondary"
> >
<option>Damaged</option> <option>Damaged</option>
<option>Expired</option> <option>Expired</option>

View File

@@ -49,7 +49,7 @@ export default function AiManager({
data-testid="provider-option" data-testid="provider-option"
onClick={() => handleUpdateAiProvider(p.id)} onClick={() => handleUpdateAiProvider(p.id)}
className={cn( className={cn(
"p-4 rounded-2xl border transition-all text-left flex items-center justify-between group", "p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 rounded-2xl border transition-all text-left flex items-center justify-between group",
p.active p.active
? "bg-primary border-primary shadow-xl shadow-primary/10" ? "bg-primary border-primary shadow-xl shadow-primary/10"
: "bg-background/60 border-slate-800 hover:border-primary/40" : "bg-background/60 border-slate-800 hover:border-primary/40"

View File

@@ -42,7 +42,7 @@ export default function CategoryManager({
<div data-testid="category-list" className="grid sm:grid-cols-2 lg:grid-cols-4 gap-2.5"> <div data-testid="category-list" className="grid sm:grid-cols-2 lg:grid-cols-4 gap-2.5">
{categories.map(cat => ( {categories.map(cat => (
<div key={cat.id} data-testid="category-item" className="p-4 bg-background/40 border border-slate-800/50 rounded-2xl flex items-center justify-between group hover:border-primary/40 transition-all"> <div key={cat.id} data-testid="category-item" className="p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 bg-background/40 border border-slate-800/50 rounded-2xl flex items-center justify-between group hover:border-primary/40 transition-all">
<div className="min-w-0 pr-4"> <div className="min-w-0 pr-4">
<p className="card-title group-hover:text-primary transition-colors">{cat.name}</p> <p className="card-title group-hover:text-primary transition-colors">{cat.name}</p>
<p className="card-subtitle">{cat.description || 'General storage'}</p> <p className="card-subtitle">{cat.description || 'General storage'}</p>
@@ -53,14 +53,14 @@ export default function CategoryManager({
setEditingCategory(cat); setEditingCategory(cat);
setEditCatForm({ name: cat.name, description: cat.description || '' }); setEditCatForm({ name: cat.name, description: cat.description || '' });
}} }}
className="p-2 text-secondary hover:text-white hover:bg-slate-800 rounded-lg transition-all" className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 text-secondary hover:text-white hover:bg-slate-800 rounded-lg transition-all"
> >
<Edit2 size={14} /> <Edit2 size={14} />
</button> </button>
<button <button
onClick={() => onDeleteCategory(cat.id, cat.name)} onClick={() => onDeleteCategory(cat.id, cat.name)}
data-testid="delete-category-button" data-testid="delete-category-button"
className="p-2 text-secondary hover:text-rose-500 hover:bg-rose-500/5 rounded-lg transition-all" className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 text-secondary hover:text-rose-500 hover:bg-rose-500/5 rounded-lg transition-all"
> >
<Trash2 size={14} /> <Trash2 size={14} />
</button> </button>

View File

@@ -144,7 +144,7 @@ export default function DatabaseManager({
</div> </div>
<button <button
onClick={() => onRestore(bak.filename)} onClick={() => onRestore(bak.filename)}
className="p-2 text-secondary hover:text-primary hover:bg-primary/5 rounded-xl transition-all opacity-0 group-hover/item:opacity-100 focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none" className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 text-secondary hover:text-primary hover:bg-primary/5 rounded-xl transition-all opacity-0 group-hover/item:opacity-100 focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none"
aria-label={`Restore backup ${bak.filename}`} aria-label={`Restore backup ${bak.filename}`}
> >
<RotateCcw size={16} /> <RotateCcw size={16} />

View File

@@ -63,7 +63,7 @@ export default function IdentityManager({
setEditingUser(user); setEditingUser(user);
setEditUserForm({ username: user.username, password: '', role: user.role }); setEditUserForm({ username: user.username, password: '', role: user.role });
}} }}
className="p-2 text-secondary hover:text-primary hover:bg-primary/5 rounded-xl transition-all focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none" className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 text-secondary hover:text-primary hover:bg-primary/5 rounded-xl transition-all focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none"
aria-label={`Edit user ${user.username}`} aria-label={`Edit user ${user.username}`}
> >
<Edit2 size={16} /> <Edit2 size={16} />
@@ -72,7 +72,7 @@ export default function IdentityManager({
<button <button
onClick={() => onDeleteUser(user.id, user.username)} onClick={() => onDeleteUser(user.id, user.username)}
data-testid="delete-user-button" data-testid="delete-user-button"
className="p-2 text-secondary hover:text-rose-500 hover:bg-rose-500/5 rounded-xl transition-all focus-visible:ring-2 focus-visible:ring-rose-500 focus-visible:outline-none" className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 text-secondary hover:text-rose-500 hover:bg-rose-500/5 rounded-xl transition-all focus-visible:ring-2 focus-visible:ring-rose-500 focus-visible:outline-none"
aria-label={`Delete user ${user.username}`} aria-label={`Delete user ${user.username}`}
> >
<Trash2 size={16} /> <Trash2 size={16} />
@@ -91,7 +91,7 @@ export default function IdentityManager({
<h3 className="text-xl font-normal text-white tracking-tight">Edit Identity</h3> <h3 className="text-xl font-normal text-white tracking-tight">Edit Identity</h3>
<button <button
onClick={() => setEditingUser(null)} onClick={() => setEditingUser(null)}
className="p-2 text-muted hover:text-white transition-colors focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none rounded" className="p-2 lg:p-3 xl:p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 text-muted hover:text-white transition-colors focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none rounded"
aria-label="Close edit dialog" aria-label="Close edit dialog"
> >
<X size={20} /> <X size={20} />

View File

@@ -28,7 +28,7 @@ export default function LdapManager({
</div> </div>
</div> </div>
<div className="flex items-center justify-between p-4 bg-background/40 border border-slate-800/60 rounded-2xl group transition-all hover:border-primary/30"> <div className="flex items-center justify-between p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 bg-background/40 border border-slate-800/60 rounded-2xl group transition-all hover:border-primary/30">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className={cn( <div className={cn(
"w-8 h-8 rounded-xl flex items-center justify-center transition-all shadow-inner", "w-8 h-8 rounded-xl flex items-center justify-center transition-all shadow-inner",
@@ -115,7 +115,7 @@ export default function LdapManager({
</div> </div>
</div> </div>
<div className="flex items-center justify-between p-4 bg-background/40 border border-slate-800/60 rounded-2xl group transition-all hover:border-primary/30"> <div className="flex items-center justify-between p-4 lg:p-5 xl:p-6 lg:p-8 xl:p-10 bg-background/40 border border-slate-800/60 rounded-2xl group transition-all hover:border-primary/30">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className={cn( <div className={cn(
"w-8 h-8 rounded-xl flex items-center justify-center transition-all shadow-inner", "w-8 h-8 rounded-xl flex items-center justify-center transition-all shadow-inner",

View File

@@ -4,40 +4,47 @@ const config: Config = {
content: [ content: [
"./app/**/*.{js,ts,jsx,tsx,mdx}", "./app/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}", "./components/**/*.{js,ts,jsx,tsx,mdx}",
"./lib/**/*.{js,ts,jsx,tsx,mdx}",
], ],
theme: { theme: {
fontSize: { fontSize: {
'xs': '13px', // increased from 12px 'xs': '12px',
'sm': '15px', // increased from 14px 'sm': '14px', // Design: label-md, mono-data
'base': '18px', // increased from 16px 'base': '16px', // Design: body-md
'lg': '20px', // increased from 18px 'lg': '18px', // Design: body-lg
'xl': '22px', // increased from 20px 'xl': '20px',
'2xl': '26px', // increased from 24px '2xl': '24px', // Design: headline-sm
'3xl': '32px', // increased from 30px '3xl': '30px',
'4xl': '36px', '4xl': '32px', // Design: headline-md
'5xl': '48px', '5xl': '48px', // Design: headline-lg
'6xl': '64px',
}, },
extend: { extend: {
fontFamily: {
sans: ["'Space Grotesk'", "sans-serif"],
},
colors: { colors: {
background: "#0A0E27", background: "#131313",
foreground: "#F0F4F8", foreground: "#FFFFFF",
surface: "#1A1F3A", surface: "#0A0A0A",
"surface-container": "#121212",
"surface-bright": "#1A1A1A",
primary: { primary: {
DEFAULT: "#3B82F6", DEFAULT: "#F58618",
foreground: "#FFFFFF", foreground: "#000000",
}, },
secondary: { secondary: {
DEFAULT: "#C7D2E0", DEFAULT: "#888888",
foreground: "#0A0E27", foreground: "#FFFFFF",
}, },
muted: { muted: {
DEFAULT: "#8B95AD", DEFAULT: "#444444",
foreground: "#F0F4F8", foreground: "#888888",
}, },
border: "#2D3561", border: "#222222",
success: "#10B981", success: "#00FF41",
error: "#EF4444", error: "#FF3131",
warning: "#F59E0B", warning: "#F58618",
}, },
keyframes: { keyframes: {
scan: { scan: {