Compare commits

...

8 Commits

Author SHA1 Message Date
3ae7e60d43 update 2026-06-16 22:54:38 -06:00
ceb04a1b83 fix: wrap workspace toolbar buttons on narrow mobile widths 2026-06-16 22:23:23 -06:00
2de18e40e2 feat: add sidebar/table hover tints and focus-visible ring for buttons 2026-06-16 22:20:06 -06:00
a0f26117a6 feat: add transitions and active-press feedback to buttons and editable fields 2026-06-16 22:18:01 -06:00
e3a21a46b5 feat: add radius/spacing design tokens and apply across styles 2026-06-16 22:15:09 -06:00
be8fe206bf docs: add UI detail polish implementation plan
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 22:11:27 -06:00
1d123bfac3 fix: add spacing between reset-password and delete buttons in user management
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 22:10:45 -06:00
59e8bfb2a9 docs: add UI detail polish design spec
Spec covering radius/spacing tokens, interaction feedback (hover/active/
focus-visible), and a mobile workspace-toolbar wrapping fix, based on
live screenshot review of the running app.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 20:03:07 -06:00
8 changed files with 844 additions and 67 deletions

3
.gitignore vendored
View File

@@ -27,3 +27,6 @@ dist-ssr
# Backend data and secrets
data/teaching-books.db
.env
# Brainstorming visual companion
.superpowers/

View File

@@ -0,0 +1,607 @@
# UI Detail Polish Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Apply the changes in `docs/superpowers/specs/2026-06-16-ui-detail-polish-design.md` — radius/spacing design tokens, subtle interaction feedback (hover/active/focus-visible), and a mobile workspace-toolbar wrapping fix.
**Architecture:** This is a CSS-only change set, almost entirely confined to `src/style.css`, plus one selector update in `src/components/LoginPage.vue`'s `<style scoped>` block. There is no component markup or behavior change, and no new dependencies.
**Tech Stack:** Vue 3 + TypeScript (Vite + Bun), plain CSS (no preprocessor, no CSS framework).
## Global Constraints
- No markup or `<script>` changes in any `.vue` file — CSS only.
- Do not touch `src/print.css` or the A4 canvas layout (`.page`, `.a4-workspace`, `.a4-paper`).
- Do not replace `window.confirm()` with the `.dialog` component anywhere (out of scope, separate future spec).
- No new npm/bun dependencies, no icon or animation libraries.
- This repo has no component test files (`bun run test` runs `vitest run` with zero test files — confirmed before writing this plan). The testable deliverable for each task is: `bun run build` passes (type-check via `vue-tsc -b` + Vite build) AND a manual visual check in the browser matches the description in that task.
- Dev server: `bun run dev` (Vite on `http://localhost:5173/`). It was already running on port 5173 at the time this plan was written — check before starting a new one (`curl -s -o /dev/null -w "%{http_code}\n" http://localhost:5173/`).
- Admin login credentials for manual checks (from `.env`): `admin` / `admin123`.
- Make one commit per task, after its build+visual check passes.
---
### Task 1: Radius and spacing tokens
**Files:**
- Modify: `src/style.css:1-14` (`:root` block), and every line listed below in the same file.
- Modify: `src/components/LoginPage.vue:76-86` (`.login-form` rule in `<style scoped>`)
**Interfaces:**
- Produces: CSS custom properties `--radius-sm`, `--radius-md`, `--radius-lg`, `--radius-xl`, `--radius-pill`, `--space-1`, `--space-2`, `--space-3`, `--space-4`, `--space-6` on `:root` in `src/style.css`. Every later task in this plan may reference these by name.
- [ ] **Step 1: Add the token block to `:root`**
In `src/style.css`, the current `:root` block is:
```css
:root {
font-family: Inter, "PingFang SC", "Microsoft YaHei", sans-serif;
color: #202a33;
background: #edf0f2;
font-synthesis: none;
text-rendering: optimizeLegibility;
--green-700: #216447;
--green-600: #2d7a58;
--green-100: #dceee5;
--line: #cfd5da;
--muted: #68747f;
--paper-width: 210mm;
--paper-min-height: 297mm;
}
```
Replace it with:
```css
:root {
font-family: Inter, "PingFang SC", "Microsoft YaHei", sans-serif;
color: #202a33;
background: #edf0f2;
font-synthesis: none;
text-rendering: optimizeLegibility;
--green-700: #216447;
--green-600: #2d7a58;
--green-100: #dceee5;
--line: #cfd5da;
--muted: #68747f;
--paper-width: 210mm;
--paper-min-height: 297mm;
--radius-sm: 4px;
--radius-md: 6px;
--radius-lg: 8px;
--radius-xl: 12px;
--radius-pill: 999px;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-6: 24px;
}
```
- [ ] **Step 2: Swap every hardcoded `border-radius` value onto the matching token (like-for-like, no visual change)**
Make these exact replacements in `src/style.css` (line numbers are pre-edit; re-find by selector if they've shifted from Step 1):
| Line | Selector | Before | After |
|---|---|---|---|
| 72 | `.ui-button` | `border-radius: 6px;` | `border-radius: var(--radius-md);` |
| 114 | `.ui-field,`<br>`.ui-select` | `border-radius: 6px;` | `border-radius: var(--radius-md);` |
| 186 | `.workspace-toolbar button` | `border-radius: 6px;` | `border-radius: var(--radius-md);` |
| 288 | `.lesson-sidebar-badge` | `border-radius: 999px;` | `border-radius: var(--radius-pill);` |
| 430 | `.process-step-actions button` | `border-radius: 4px;` | `border-radius: var(--radius-sm);` |
| 446 | `.board-design` | `border-radius: 4px;` | `border-radius: var(--radius-sm);` |
| 456 | `.warning-summary` | `border-radius: 4px;` | `border-radius: var(--radius-sm);` |
| 472 | `.editable-text` | `border-radius: 4px;` | `border-radius: var(--radius-sm);` |
| 507 | `.markdown-preview` | `border-radius: 4px;` | `border-radius: var(--radius-sm);` |
| 532 | `.markdown-source` | `border-radius: 4px;` | `border-radius: var(--radius-sm);` |
| 551 | `.upload-dropzone` | `border-radius: 12px;` | `border-radius: var(--radius-xl);` |
| 569 | `.upload-dropzone--compact` | `border-radius: 6px;` | `border-radius: var(--radius-md);` |
| 603 | `.dialog` | `border-radius: 8px;` | `border-radius: var(--radius-lg);` |
| 625 | `.dialog-actions button` | `border-radius: 6px;` | `border-radius: var(--radius-md);` |
| 648 | `.app-notice button` | `border-radius: 4px;` | `border-radius: var(--radius-sm);` |
| 697 | `.dialog input` | `border-radius: 6px;` | `border-radius: var(--radius-md);` |
| 718 | `.book-list-item` | `border-radius: 8px;` | `border-radius: var(--radius-lg);` |
| 741 | `.batch-topics-input` | `border-radius: 6px;` | `border-radius: var(--radius-md);` |
Leave `.batch-progress-bar` and `.batch-progress-fill` (`border-radius: 3px`) unchanged — 3px isn't on the new scale and isn't called out in the spec.
- [ ] **Step 3: Swap the radius in `LoginPage.vue`**
In `src/components/LoginPage.vue`, inside `<style scoped>`, change:
```css
.login-form {
width: min(100%, 340px);
display: flex;
flex-direction: column;
gap: 16px;
background: #fff;
border: 1px solid var(--line);
border-radius: 8px;
box-shadow: 0 4px 18px rgba(32, 42, 51, 0.12);
padding: 24px;
}
```
to:
```css
.login-form {
width: min(100%, 340px);
display: flex;
flex-direction: column;
gap: 16px;
background: #fff;
border: 1px solid var(--line);
border-radius: var(--radius-lg);
box-shadow: 0 4px 18px rgba(32, 42, 51, 0.12);
padding: 24px;
}
```
- [ ] **Step 4: Snap the three off-grid spacing values**
In `src/components/LoginPage.vue`, change `.field`:
```css
.field {
display: flex;
flex-direction: column;
gap: 6px;
}
```
to:
```css
.field {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
```
In `src/style.css`, change `.ui-table th, .ui-table td`:
```css
.ui-table th,
.ui-table td {
text-align: left;
padding: 8px 10px;
border-bottom: 1px solid var(--line);
}
```
to:
```css
.ui-table th,
.ui-table td {
text-align: left;
padding: var(--space-2) var(--space-2);
border-bottom: 1px solid var(--line);
}
```
In `src/style.css`, change `.objective-row`:
```css
.objective-row {
display: flex;
align-items: baseline;
gap: 6px;
}
```
to:
```css
.objective-row {
display: flex;
align-items: baseline;
gap: var(--space-2);
}
```
- [ ] **Step 5: Build and verify no visual regression**
Run: `bun run build`
Expected: exits 0, no TypeScript or Vue compiler errors.
Then, with the dev server running at `http://localhost:5173/`, open the app in a browser and compare against the pre-change appearance (radius values are identical, so nothing should look different):
- Login page (`/`): card corners, input corners.
- Book list and admin page (login as `admin`/`admin123`): buttons, table.
- Open a lesson workspace: toolbar buttons, sidebar badge, editable text fields, dialogs (e.g. open the batch-generate dialog).
Confirm visually that nothing shifted except the three intentional spacing snaps (table cell padding, login field label gap, objective row gap — all changed by at most 2px and should not be perceptible as broken).
- [ ] **Step 6: Commit**
```bash
git add src/style.css src/components/LoginPage.vue
git commit -m "feat: add radius/spacing design tokens and apply across styles"
```
---
### Task 2: Transitions and active states on buttons/editable elements
**Files:**
- Modify: `src/style.css` (selectors listed below)
**Interfaces:**
- Consumes: `--green-100`, `--green-600`, `--green-700` (existing tokens).
- Produces: every button-like selector listed below now has a `transition` declaration and an `:active:not(:disabled)` rule. Task 3 will add `:focus-visible` to the same selector group — keep the group identical so the two tasks compose cleanly: `.ui-button`, `.workspace-toolbar button`, `.dialog-actions button`, `.process-step-actions button`.
- [ ] **Step 1: Add transitions to button selectors**
In `src/style.css`, change:
```css
.ui-button {
border: 1px solid var(--line);
background: #fff;
border-radius: var(--radius-md);
padding: 6px 14px;
color: var(--green-700);
cursor: pointer;
white-space: nowrap;
}
```
to:
```css
.ui-button {
border: 1px solid var(--line);
background: #fff;
border-radius: var(--radius-md);
padding: 6px 14px;
color: var(--green-700);
cursor: pointer;
white-space: nowrap;
transition: background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
```
Change:
```css
.workspace-toolbar button {
border: 1px solid var(--line);
background: #fff;
border-radius: var(--radius-md);
padding: 6px 14px;
color: var(--green-700);
cursor: pointer;
}
```
to:
```css
.workspace-toolbar button {
border: 1px solid var(--line);
background: #fff;
border-radius: var(--radius-md);
padding: 6px 14px;
color: var(--green-700);
cursor: pointer;
transition: background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
```
Change:
```css
.dialog-actions button {
border: 1px solid var(--line);
background: #fff;
border-radius: var(--radius-md);
padding: 6px 14px;
cursor: pointer;
}
```
to:
```css
.dialog-actions button {
border: 1px solid var(--line);
background: #fff;
border-radius: var(--radius-md);
padding: 6px 14px;
cursor: pointer;
transition: background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
```
Change:
```css
.process-step-actions button {
border: 1px solid var(--line);
background: #fff;
border-radius: var(--radius-sm);
padding: 2px 6px;
font-size: 12px;
cursor: pointer;
color: #c0392b;
}
```
to:
```css
.process-step-actions button {
border: 1px solid var(--line);
background: #fff;
border-radius: var(--radius-sm);
padding: 2px 6px;
font-size: 12px;
cursor: pointer;
color: #c0392b;
transition: background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
```
Change:
```css
.editable-text {
display: block;
width: 100%;
border: 1px solid transparent;
border-radius: var(--radius-sm);
padding: 2px 4px;
background: transparent;
resize: none;
overflow: hidden;
}
```
to:
```css
.editable-text {
display: block;
width: 100%;
border: 1px solid transparent;
border-radius: var(--radius-sm);
padding: 2px 4px;
background: transparent;
resize: none;
overflow: hidden;
transition: background-color 0.15s ease, border-color 0.15s ease;
}
```
Change:
```css
.markdown-preview {
min-height: 1.6em;
padding: 2px 4px;
border-radius: var(--radius-sm);
border: 1px solid transparent;
cursor: text;
}
```
to:
```css
.markdown-preview {
min-height: 1.6em;
padding: 2px 4px;
border-radius: var(--radius-sm);
border: 1px solid transparent;
cursor: text;
transition: background-color 0.15s ease, border-color 0.15s ease;
}
```
- [ ] **Step 2: Add `:active:not(:disabled)` feedback to button selectors**
Add this new rule directly after the `.ui-button--danger:hover:not(:disabled)` rule (currently ending around line 109):
```css
.ui-button:active:not(:disabled),
.workspace-toolbar button:active:not(:disabled),
.dialog-actions button:active:not(:disabled),
.process-step-actions button:active:not(:disabled) {
filter: brightness(0.95);
}
```
Using `filter: brightness()` gives every button variant (default, primary, danger) a consistent "pressed" darkening with one rule, instead of hand-picking a darker shade per variant.
- [ ] **Step 3: Build and verify**
Run: `bun run build`
Expected: exits 0.
In the browser, hover and click (mouse-down, hold) each of: a `.ui-button` (e.g. "返回" on admin page), a workspace toolbar button, a dialog button (open any dialog), a process-step action button (in a lesson's process table), an editable text field, and a markdown preview block. Confirm:
- Hover transitions smoothly (no instant snap) over ~150ms.
- Mouse-down on buttons visibly darkens the button while held.
- [ ] **Step 4: Commit**
```bash
git add src/style.css
git commit -m "feat: add transitions and active-press feedback to buttons and editable fields"
```
---
### Task 3: New hover states and shared focus-visible ring
**Files:**
- Modify: `src/style.css`
**Interfaces:**
- Consumes: the button selector group from Task 2 (`.ui-button`, `.workspace-toolbar button`, `.dialog-actions button`, `.process-step-actions button`) and `--green-600`.
- Produces: nothing consumed by later tasks.
- [ ] **Step 1: Add hover tint to non-active sidebar items**
In `src/style.css`, locate `.lesson-sidebar-select` (currently no hover rule) and add a new rule immediately after it:
```css
.lesson-sidebar-select {
flex: 1 1 auto;
display: flex;
align-items: center;
gap: 8px;
border: none;
background: none;
text-align: left;
padding: 10px 12px;
cursor: pointer;
min-width: 0;
transition: background-color 0.15s ease;
}
.lesson-sidebar-item:not(.lesson-sidebar-item--active) .lesson-sidebar-select:hover {
background: #f4f6f7;
}
```
(The `:not(.lesson-sidebar-item--active)` guard keeps this from fighting with the existing green `--active` background on the selected item.)
- [ ] **Step 2: Add hover tint to table rows**
In `src/style.css`, locate the `.ui-table tr:last-child td` rule and add a new rule right after it:
```css
.ui-table tr:last-child td {
border-bottom: none;
}
.ui-table tbody tr:hover {
background: #f8faf9;
}
```
- [ ] **Step 3: Add shared `:focus-visible` ring for buttons**
In `src/style.css`, add this new rule right after the `:active:not(:disabled)` rule added in Task 2 Step 2:
```css
.ui-button:focus-visible,
.workspace-toolbar button:focus-visible,
.dialog-actions button:focus-visible,
.process-step-actions button:focus-visible {
outline: none;
border-color: var(--green-600);
box-shadow: 0 0 0 2px rgba(45, 122, 88, 0.16);
}
```
- [ ] **Step 4: Build and verify**
Run: `bun run build`
Expected: exits 0.
In the browser:
- Open a lesson with multiple chapters in the sidebar. Hover a non-active sidebar row → light gray background appears; hover does NOT appear on the currently active (green) row.
- Hover over admin/book-list table rows → light tint appears per row.
- Tab (keyboard) through buttons (e.g. admin page header buttons) → a green focus ring appears on the focused button. Click a button with the mouse → no ring appears on click (only on keyboard focus), confirming `:focus-visible` rather than `:focus` is in effect.
- [ ] **Step 5: Commit**
```bash
git add src/style.css
git commit -m "feat: add sidebar/table hover tints and focus-visible ring for buttons"
```
---
### Task 4: Mobile workspace toolbar wrap fix
**Files:**
- Modify: `src/style.css` (the existing `@media (max-width: 900px)` block, by adding a new sibling media block — do not nest inside it)
**Interfaces:**
- Consumes: `--space-2`, `--space-4` from Task 1.
- Produces: nothing consumed by later tasks. This is the last task in the plan.
- [ ] **Step 1: Add the new breakpoint**
In `src/style.css`, the existing responsive block currently ends with:
```css
@media (max-width: 900px) {
.workspace-layout {
flex-direction: column;
}
.lesson-sidebar {
width: auto;
flex: 0 0 auto;
max-height: 180px;
border-right: none;
border-bottom: 1px solid var(--line);
}
.a4-workspace {
padding: 6mm;
}
.page {
width: 100%;
min-height: auto;
}
}
```
Add a new, separate media block directly after this one (same `/* Responsive */` section, not nested inside the 900px block):
```css
@media (max-width: 600px) {
.workspace-toolbar {
height: auto;
flex: 0 0 auto;
flex-wrap: wrap;
padding: var(--space-2) var(--space-4);
gap: var(--space-2);
}
.workspace-toolbar button {
flex: 0 0 auto;
}
.workspace-toolbar-count {
flex: 1 1 100%;
}
}
```
- [ ] **Step 2: Build**
Run: `bun run build`
Expected: exits 0.
- [ ] **Step 3: Verify at 390px width**
With the dev server running, open a lesson workspace in the browser and resize/emulate the viewport to 390px wide (e.g. iPhone 12 Pro preset). Confirm:
- The toolbar's 6 buttons wrap onto two rows, each button keeps its full label on one line (no character-by-character wrapping).
- The "已选 N 个" count text drops to its own line below the buttons.
- At desktop width (e.g. 1280px), the toolbar is unchanged from before (single row, fixed 56px height).
- [ ] **Step 4: Commit**
```bash
git add src/style.css
git commit -m "fix: wrap workspace toolbar buttons on narrow mobile widths"
```
---
## Self-Review Notes
- **Spec coverage:** Section 1 (tokens + 3 spacing snaps) → Task 1. Section 2 (transitions, active states, new hovers, focus-visible) → Tasks 23. Section 3 (mobile toolbar) → Task 4. Testing section (build + manual desktop/390px check) → covered in every task's verification step.
- **Out-of-scope guardrails carried into Global Constraints:** no `window.confirm()``.dialog` migration, no A4/`print.css` changes, no new dependencies, no markup changes — all stated explicitly so a task executor with zero conversation context doesn't drift into them.
- **Type/selector consistency:** the four-selector "button group" (`.ui-button`, `.workspace-toolbar button`, `.dialog-actions button`, `.process-step-actions button`) is used identically across Task 2's transitions/active rule and Task 3's focus-visible rule — verified the selector list matches verbatim in both places.

View File

@@ -0,0 +1,97 @@
# UI Detail Polish Design
## Goal
Polish UI details across the whole app: interaction feedback, spacing/radius consistency, and a real mobile layout bug in the workspace toolbar. Found via live screenshots of the running app (login, book list, admin, workspace, dialogs) at desktop and 390px mobile width.
## Scope
In scope:
- `src/style.css`: design tokens for border-radius and spacing, transitions, hover/active/focus-visible states, table/sidebar hover tints, mobile toolbar fix.
- Any component `<style scoped>` blocks that hardcode a radius/spacing value covered by the new tokens (e.g. `AdminPage.vue`, `LoginPage.vue`, dialog components).
Out of scope:
- Replacing native `window.confirm()` (used for delete in `BookListPage.vue` and `AdminPage.vue`) with the app's custom `.dialog` component. This is a behavior/component-level change, not a styling detail — flagged for a future, separate spec.
- Redesigning the A4 teaching-design canvas or `src/print.css`.
- Adding icons, animation libraries, or new dependencies.
- Changing any component's markup structure or behavior, except where strictly required to apply a hover/focus class.
## 1. Radius and Spacing Tokens
Add to `:root` in `src/style.css`:
```css
--radius-sm: 4px; /* inline controls: editable-text, markdown-preview/source, board-design, process-step-actions button */
--radius-md: 6px; /* default control radius: ui-button, ui-field, ui-select, workspace-toolbar button, dialog-actions button */
--radius-lg: 8px; /* cards/surfaces: dialog, login-form */
--radius-xl: 12px; /* upload-dropzone */
--radius-pill: 999px; /* lesson-sidebar-badge */
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-6: 24px;
```
Replace existing hardcoded radius values with the matching variable everywhere they appear in `src/style.css` and in component `<style scoped>` blocks (`AdminPage.vue`, `LoginPage.vue`, dialog components). This is a like-for-like swap — current values already map cleanly onto this scale, so visual output should not change except for the spacing snap below.
Snap off-grid spacing values onto the 4px scale where they don't change layout meaningfully:
- `.field` gap `6px``var(--space-2)` (8px)
- `.ui-table th/td` padding `8px 10px``var(--space-2) var(--space-2)` (8px 8px)
- `.objective-row` gap `6px``var(--space-2)` (8px)
Skip snapping any value where it would visibly break an intentional layout (e.g. `process-step-actions { width: 6em }` is a width, not a spacing token, and stays as-is).
## 2. Interaction Feedback (subtle intensity)
Apply `transition: background-color .15s ease, border-color .15s ease, box-shadow .15s ease;` to:
- `.ui-button` (covers `--primary` and `--danger` variants)
- `.workspace-toolbar button`
- `.dialog-actions button`
- `.process-step-actions button`
- `.editable-text`, `.markdown-preview` (already have hover background, currently instant)
Add `:active:not(:disabled)` states (one shade darker / more saturated than hover) to the same button selectors above, so clicks feel acknowledged.
Add hover feedback where there is currently none:
- `.lesson-sidebar-select:not(.lesson-sidebar-item--active *)`: light neutral hover background (e.g. `#f4f6f7`), distinct from the existing green `--active` state, transition included.
- `.ui-table tbody tr:hover`: light tint (e.g. `var(--green-100)` at reduced opacity or `#f8faf9`) for scanability.
Add a shared `:focus-visible` style for buttons, matching the existing `.ui-field:focus` treatment (`border-color: var(--green-600); box-shadow: 0 0 0 2px rgba(45, 122, 88, 0.16);`) so keyboard focus is visually consistent between buttons and inputs. Mouse clicks should not trigger this (hence `:focus-visible`, not `:focus`).
## 3. Mobile Workspace Toolbar Fix
Current bug: `.workspace-toolbar` is a fixed-height (56px) single-row flex container with no wrapping. At ~390px width its 6 buttons compress until button text wraps character-by-character, making the toolbar unreadable and unusable.
Fix (selected option B — wrap): add a breakpoint at `max-width: 600px`:
```css
@media (max-width: 600px) {
.workspace-toolbar {
height: auto;
flex: 0 0 auto;
flex-wrap: wrap;
padding: var(--space-2) var(--space-4);
gap: var(--space-2);
}
.workspace-toolbar button {
flex: 0 0 auto;
}
.workspace-toolbar-count {
flex: 1 1 100%;
}
}
```
Verified by live injection at 390px: all 6 buttons stay on readable single-line labels across two rows, count text drops to its own line below.
## Testing
- Run existing component tests (no behavior or markup changes expected to break them).
- Run `bun run build` (includes `vue-tsc -b`) to confirm no type errors from any `<style scoped>` edits.
- Manually verify in the browser at desktop width and at 390px width (workspace toolbar, sidebar hover, table hover, button focus-visible ring).

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>教学设计</title>
<title>教学设计生成器</title>
</head>
<body>
<div id="app"></div>

View File

@@ -176,4 +176,8 @@ onMounted(loadUsers)
flex-wrap: wrap;
align-items: center;
}
.user-list table td .ui-button + .ui-button {
margin-left: 8px;
}
</style>

View File

@@ -28,7 +28,7 @@ async function handleSubmit(): Promise<void> {
<template>
<div class="login-wrapper">
<form class="login-form" @submit.prevent="handleSubmit">
<h1>教学设计</h1>
<h1>教学设计生成器</h1>
<div class="field">
<label for="username">用户名</label>
<input
@@ -80,7 +80,7 @@ async function handleSubmit(): Promise<void> {
gap: 16px;
background: #fff;
border: 1px solid var(--line);
border-radius: 8px;
border-radius: var(--radius-lg);
box-shadow: 0 4px 18px rgba(32, 42, 51, 0.12);
padding: 24px;
}
@@ -95,7 +95,7 @@ async function handleSubmit(): Promise<void> {
.field {
display: flex;
flex-direction: column;
gap: 6px;
gap: var(--space-2);
}
.field label {

View File

@@ -40,6 +40,12 @@
box-shadow: none;
}
.basic-info-table,
.process-table,
.reflection-table {
width: calc(100% - 1px);
}
.process-table {
break-inside: auto;
}

View File

@@ -11,6 +11,16 @@
--muted: #68747f;
--paper-width: 210mm;
--paper-min-height: 297mm;
--radius-sm: 4px;
--radius-md: 6px;
--radius-lg: 8px;
--radius-xl: 12px;
--radius-pill: 999px;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-6: 24px;
}
* {
@@ -69,11 +79,12 @@ input {
.ui-button {
border: 1px solid var(--line);
background: #fff;
border-radius: 6px;
border-radius: var(--radius-md);
padding: 6px 14px;
color: var(--green-700);
cursor: pointer;
white-space: nowrap;
transition: background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.ui-button:hover:not(:disabled) {
@@ -108,10 +119,26 @@ input {
border-color: #c0392b;
}
.ui-button:active:not(:disabled),
.workspace-toolbar button:active:not(:disabled),
.dialog-actions button:active:not(:disabled),
.process-step-actions button:active:not(:disabled) {
filter: brightness(0.95);
}
.ui-button:focus-visible,
.workspace-toolbar button:focus-visible,
.dialog-actions button:focus-visible,
.process-step-actions button:focus-visible {
outline: none;
border-color: var(--green-600);
box-shadow: 0 0 0 2px rgba(45, 122, 88, 0.16);
}
.ui-field,
.ui-select {
border: 1px solid var(--line);
border-radius: 6px;
border-radius: var(--radius-md);
padding: 8px 12px;
background: #fff;
color: #202a33;
@@ -131,6 +158,19 @@ input {
cursor: not-allowed;
}
.ui-error {
color: #c0392b;
font-size: 14px;
margin: 8px 0 0;
}
.ui-success {
color: var(--green-700);
font-size: 14px;
margin: 8px 0 0;
}
/* Tables */
.ui-table {
width: 100%;
border-collapse: collapse;
@@ -142,7 +182,7 @@ input {
.ui-table th,
.ui-table td {
text-align: left;
padding: 8px 10px;
padding: var(--space-2) var(--space-2);
border-bottom: 1px solid var(--line);
}
@@ -156,16 +196,44 @@ input {
border-bottom: none;
}
.ui-error {
color: #c0392b;
font-size: 14px;
margin: 8px 0 0;
.ui-table tbody tr:hover {
background: #f8faf9;
}
.ui-success {
table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
.basic-info-table th,
.basic-info-table td,
.process-table th,
.process-table td,
.reflection-table th,
.reflection-table td {
border: 1px solid var(--line);
padding: 6px 8px;
vertical-align: top;
text-align: left;
}
.basic-info-table th,
.process-table th,
.reflection-table th {
background: var(--green-100);
color: var(--green-700);
font-size: 14px;
margin: 8px 0 0;
font-weight: 600;
width: 8em;
}
.process-table th {
width: auto;
}
.process-step-actions {
width: 6em;
text-align: center;
}
/* Toolbar */
@@ -183,10 +251,11 @@ input {
.workspace-toolbar button {
border: 1px solid var(--line);
background: #fff;
border-radius: 6px;
border-radius: var(--radius-md);
padding: 6px 14px;
color: var(--green-700);
cursor: pointer;
transition: background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.workspace-toolbar button:hover:not(:disabled) {
@@ -266,6 +335,11 @@ input {
padding: 10px 12px;
cursor: pointer;
min-width: 0;
transition: background-color 0.15s ease;
}
.lesson-sidebar-item:not(.lesson-sidebar-item--active) .lesson-sidebar-select:hover {
background: #f4f6f7;
}
.lesson-sidebar-number {
@@ -285,7 +359,7 @@ input {
flex: 0 0 auto;
background: #e67e22;
color: #fff;
border-radius: 999px;
border-radius: var(--radius-pill);
font-size: 12px;
line-height: 1.6;
min-width: 1.6em;
@@ -366,37 +440,6 @@ input {
color: var(--green-700);
}
table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
.basic-info-table th,
.basic-info-table td,
.process-table th,
.process-table td,
.reflection-table th,
.reflection-table td {
border: 1px solid var(--line);
padding: 6px 8px;
vertical-align: top;
text-align: left;
}
.basic-info-table th,
.process-table th,
.reflection-table th {
background: var(--green-100);
color: var(--green-700);
font-weight: 600;
width: 8em;
}
.process-table th {
width: auto;
}
.objectives-cell {
display: flex;
flex-direction: column;
@@ -406,7 +449,7 @@ table {
.objective-row {
display: flex;
align-items: baseline;
gap: 6px;
gap: var(--space-2);
}
.objective-label {
@@ -419,19 +462,15 @@ table {
margin-top: 4px;
}
.process-step-actions {
width: 6em;
text-align: center;
}
.process-step-actions button {
border: 1px solid var(--line);
background: #fff;
border-radius: 4px;
border-radius: var(--radius-sm);
padding: 2px 6px;
font-size: 12px;
cursor: pointer;
color: #c0392b;
transition: background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.process-step-actions button:disabled {
@@ -443,7 +482,7 @@ table {
font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
white-space: pre-wrap;
border: 1px solid var(--line);
border-radius: 4px;
border-radius: var(--radius-sm);
padding: 8px;
min-height: 6em;
}
@@ -453,7 +492,7 @@ table {
padding: 8px 12px;
border: 1px solid #e6c98b;
background: #fbf3e1;
border-radius: 4px;
border-radius: var(--radius-sm);
color: #8a6116;
font-size: 13px;
}
@@ -469,11 +508,12 @@ table {
display: block;
width: 100%;
border: 1px solid transparent;
border-radius: 4px;
border-radius: var(--radius-sm);
padding: 2px 4px;
background: transparent;
resize: none;
overflow: hidden;
transition: background-color 0.15s ease, border-color 0.15s ease;
}
.editable-text--multiline {
@@ -504,9 +544,10 @@ table {
.markdown-preview {
min-height: 1.6em;
padding: 2px 4px;
border-radius: 4px;
border-radius: var(--radius-sm);
border: 1px solid transparent;
cursor: text;
transition: background-color 0.15s ease, border-color 0.15s ease;
}
.markdown-preview--empty {
@@ -529,7 +570,7 @@ table {
display: block;
width: 100%;
border: 1px solid var(--green-600);
border-radius: 4px;
border-radius: var(--radius-sm);
padding: 2px 4px;
background: #fff;
resize: none;
@@ -548,7 +589,7 @@ table {
max-width: 480px;
min-height: 200px;
border: 2px dashed var(--line);
border-radius: 12px;
border-radius: var(--radius-xl);
background: #fff;
color: var(--muted);
text-align: center;
@@ -566,7 +607,7 @@ table {
min-height: 0;
margin: 0;
padding: 6px 14px;
border-radius: 6px;
border-radius: var(--radius-md);
}
.upload-dropzone-input {
@@ -600,7 +641,7 @@ table {
.dialog {
background: #fff;
border-radius: 8px;
border-radius: var(--radius-lg);
padding: 24px;
max-width: 420px;
box-shadow: 0 12px 32px rgba(32, 42, 51, 0.25);
@@ -622,9 +663,10 @@ table {
.dialog-actions button {
border: 1px solid var(--line);
background: #fff;
border-radius: 6px;
border-radius: var(--radius-md);
padding: 6px 14px;
cursor: pointer;
transition: background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.app-notice {
@@ -645,7 +687,7 @@ table {
.app-notice button {
border: 1px solid currentcolor;
background: none;
border-radius: 4px;
border-radius: var(--radius-sm);
padding: 2px 8px;
cursor: pointer;
color: inherit;
@@ -684,6 +726,24 @@ table {
}
}
@media (max-width: 600px) {
.workspace-toolbar {
height: auto;
flex: 0 0 auto;
flex-wrap: wrap;
padding: var(--space-2) var(--space-4);
gap: var(--space-2);
}
.workspace-toolbar button {
flex: 0 0 auto;
}
.workspace-toolbar-count {
flex: 1 1 100%;
}
}
/* Book list */
.book-list-create {
display: flex;
@@ -694,7 +754,7 @@ table {
.dialog input {
flex: 1 1 auto;
border: 1px solid var(--line);
border-radius: 6px;
border-radius: var(--radius-md);
padding: 8px 12px;
width: 100%;
}
@@ -715,7 +775,7 @@ table {
padding: 12px 16px;
background: #fff;
border: 1px solid var(--line);
border-radius: 8px;
border-radius: var(--radius-lg);
}
.book-list-name {
@@ -738,7 +798,7 @@ table {
display: block;
width: 100%;
border: 1px solid var(--line);
border-radius: 6px;
border-radius: var(--radius-md);
padding: 8px 12px;
resize: vertical;
margin-top: 8px;