initial commit
This commit is contained in:
@@ -0,0 +1,382 @@
|
||||
# Scenario [Number]: [Scenario Name] - Prototype Roadmap
|
||||
|
||||
**Scenario**: [Scenario Name]
|
||||
**Pages**: [First] through [Last]
|
||||
**Device Compatibility**: [Type] ([Width range])
|
||||
**Last Updated**: [Date]
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Scenario Overview
|
||||
|
||||
**User Journey**: [Brief description of complete user flow]
|
||||
|
||||
**Pages in this Scenario**:
|
||||
1. [Page 1] - [Description]
|
||||
2. [Page 2] - [Description]
|
||||
3. [Page 3] - [Description]
|
||||
...
|
||||
|
||||
---
|
||||
|
||||
## 📱 Device Compatibility
|
||||
|
||||
**Type**: [Mobile-Only | Mobile + Tablet | Fully Responsive | Desktop-Only]
|
||||
|
||||
**Reasoning**:
|
||||
[Why this device compatibility was chosen for this scenario]
|
||||
|
||||
**Test Viewports**:
|
||||
- [Device 1] ([width]px × [height]px) - [Purpose]
|
||||
- [Device 2] ([width]px × [height]px) - [Purpose]
|
||||
- [Device 3] ([width]px × [height]px) - [Purpose]
|
||||
|
||||
**Optimization Strategy**:
|
||||
- ✅ [Optimization 1]
|
||||
- ✅ [Optimization 2]
|
||||
- ✅ [Optimization 3]
|
||||
- ❌ [Not included 1]
|
||||
- ❌ [Not included 2]
|
||||
|
||||
**Tailwind Approach**:
|
||||
```html
|
||||
<!-- [Brief description of Tailwind strategy] -->
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 Folder Structure
|
||||
|
||||
**HTML Files** (root level - double-click to open):
|
||||
```
|
||||
[Page-1].html
|
||||
[Page-2].html
|
||||
[Page-3].html
|
||||
...
|
||||
```
|
||||
|
||||
**Supporting Folders**:
|
||||
- `shared/` - Shared code (ONE COPY for all pages)
|
||||
- `components/` - Reusable UI components (ONE COPY)
|
||||
- `pages/` - Page-specific scripts (only for complex pages)
|
||||
- `data/` - Demo data (auto-loads on first use)
|
||||
- `stories/` - Section development documentation
|
||||
- `work/` - Planning files (work.yaml for each page)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### For Testing
|
||||
1. **Open** `[First-Page].html` (double-click)
|
||||
2. **Demo data prompt** → Click YES
|
||||
3. **Navigate** through the flow
|
||||
4. **Data persists** across pages (sessionStorage)
|
||||
|
||||
### For Stakeholders
|
||||
1. **Unzip** the Prototype folder
|
||||
2. **Open** `[First-Page].html`
|
||||
3. **Test** complete user journey
|
||||
4. **Share feedback**
|
||||
|
||||
### For Developers
|
||||
1. **Review** `work/` folder for specifications
|
||||
2. **Check** `stories/` folder for implementation details
|
||||
3. **Examine** `shared/prototype-api.js` for data operations
|
||||
4. **Extract** HTML/Tailwind structure
|
||||
5. **Migrate** to production (see TODOs in code)
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Shared Resources (No Duplication!)
|
||||
|
||||
### `shared/prototype-api.js`
|
||||
**Used by**: ALL prototypes
|
||||
**Purpose**: API abstraction layer (simulates backend with sessionStorage)
|
||||
|
||||
**Key methods**:
|
||||
```javascript
|
||||
PrototypeAPI.getUser()
|
||||
PrototypeAPI.createUserProfile(userData)
|
||||
PrototypeAPI.createFamily(familyData)
|
||||
PrototypeAPI.addDog(dogData)
|
||||
// ... see file for complete API
|
||||
```
|
||||
|
||||
**Console commands** (for debugging):
|
||||
```javascript
|
||||
PrototypeAPI.getDebugInfo() // See current state
|
||||
PrototypeAPI.clearAllData() // Reset everything
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `shared/init.js`
|
||||
**Used by**: ALL prototypes
|
||||
**Purpose**: Auto-initialization (loads demo data, sets up page)
|
||||
|
||||
**What it does**:
|
||||
- Checks if demo data exists
|
||||
- Loads from `data/demo-data.json` if empty
|
||||
- Calls `window.initPage()` if defined
|
||||
- Logs current state to console
|
||||
|
||||
---
|
||||
|
||||
### `shared/utils.js`
|
||||
**Used by**: ALL prototypes
|
||||
**Purpose**: Helper functions (date formatting, validation, etc.)
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Components (Reusable - ONE COPY)
|
||||
|
||||
### `components/image-crop.js`
|
||||
**Used by**: [Pages that use image upload]
|
||||
**Purpose**: Image upload with circular crop
|
||||
|
||||
**Usage**:
|
||||
```javascript
|
||||
ImageCrop.cropImage(file, { aspectRatio: 1 });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `components/toast.js`
|
||||
**Used by**: [Pages with notifications]
|
||||
**Purpose**: Success/error toast notifications
|
||||
|
||||
**Usage**:
|
||||
```javascript
|
||||
showToast('Success message!', 'success');
|
||||
showToast('Error message', 'error');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `components/modal.js`
|
||||
**Used by**: [Pages with modals]
|
||||
**Purpose**: Generic modal overlay
|
||||
|
||||
---
|
||||
|
||||
### `components/form-validation.js`
|
||||
**Used by**: [Pages with forms]
|
||||
**Purpose**: Real-time form validation
|
||||
|
||||
---
|
||||
|
||||
## 📊 Demo Data
|
||||
|
||||
### `data/demo-data.json`
|
||||
**Purpose**: Complete demo dataset for scenario
|
||||
|
||||
**Contents**:
|
||||
- User profile
|
||||
- Family data
|
||||
- [Other data entities]
|
||||
|
||||
**Edit this file** to change demo data (JSON format, designer-friendly)
|
||||
|
||||
---
|
||||
|
||||
### `data/[additional-data].json`
|
||||
**Purpose**: [Description]
|
||||
|
||||
---
|
||||
|
||||
## 📋 Prototype Status
|
||||
|
||||
| Page | Status | Sections | Last Updated | Notes |
|
||||
|------|--------|----------|--------------|-------|
|
||||
| [Page 1] | ✅ Complete | 3/3 | [Date] | - |
|
||||
| [Page 2] | ✅ Complete | 4/4 | [Date] | - |
|
||||
| [Page 3] | 🚧 In Progress | 2/5 | [Date] | Building form fields |
|
||||
| [Page 4] | ⏸️ Not Started | 0/6 | - | Planned |
|
||||
|
||||
**Status Legend**:
|
||||
- ✅ Complete - All sections done, tested, approved
|
||||
- 🚧 In Progress - Currently building section-by-section
|
||||
- ⏸️ Not Started - Planned, not yet started
|
||||
- 🔴 Blocked - Waiting on dependency
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Development Workflow
|
||||
|
||||
### 1. Planning Phase
|
||||
- Create work file: `work/[Page]-Work.yaml`
|
||||
- Define sections (4-8 per page)
|
||||
- Identify Object IDs
|
||||
- List demo data needs
|
||||
- Get approval
|
||||
|
||||
### 2. Implementation Phase
|
||||
- Build section-by-section
|
||||
- Create story files just-in-time
|
||||
- Test after each section
|
||||
- Get approval before next section
|
||||
- File lives in root from start (no temp folder)
|
||||
|
||||
### 3. Finalization Phase
|
||||
- Complete integration test
|
||||
- Update status to Complete
|
||||
- Document any changes
|
||||
- Update this roadmap
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing Requirements
|
||||
|
||||
### Functional Testing (All Pages)
|
||||
- [ ] All form fields work
|
||||
- [ ] Validation shows errors correctly
|
||||
- [ ] Submit buttons work with loading states
|
||||
- [ ] Success/error feedback displays
|
||||
- [ ] Navigation works (back, next, cancel)
|
||||
- [ ] Data persists across pages
|
||||
|
||||
### Device Testing
|
||||
- [ ] [Primary viewport] ([width]px)
|
||||
- [ ] [Secondary viewport] ([width]px)
|
||||
- [ ] [Tertiary viewport] ([width]px)
|
||||
- [ ] Portrait orientation
|
||||
- [ ] Touch interactions work
|
||||
- [ ] No horizontal scroll
|
||||
|
||||
### Browser Testing
|
||||
- [ ] Chrome (primary)
|
||||
- [ ] Safari (iOS/Mac)
|
||||
- [ ] Firefox
|
||||
- [ ] Edge
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Tailwind Reference
|
||||
|
||||
### Project Colors
|
||||
```javascript
|
||||
// Tailwind config (in each HTML file)
|
||||
'[project-name]': {
|
||||
50: '#eff6ff',
|
||||
500: '#2563eb',
|
||||
600: '#1d4ed8',
|
||||
700: '#1e40af',
|
||||
}
|
||||
```
|
||||
|
||||
**Usage**: `bg-[project-name]-600`, `text-[project-name]-500`, etc.
|
||||
|
||||
### Common Patterns
|
||||
|
||||
**Form Input**:
|
||||
```html
|
||||
<input class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[project]-500">
|
||||
```
|
||||
|
||||
**Primary Button**:
|
||||
```html
|
||||
<button class="w-full py-3 bg-[project]-600 text-white rounded-lg font-semibold hover:bg-[project]-700 transition-colors">
|
||||
```
|
||||
|
||||
**Toast Notification**:
|
||||
```html
|
||||
<div class="fixed bottom-6 left-1/2 -translate-x-1/2 bg-gray-900 text-white px-6 py-3 rounded-lg shadow-lg">
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Issue: Demo data not loading
|
||||
**Solution**: Check `data/demo-data.json` exists, check console for errors
|
||||
|
||||
### Issue: Tailwind not working
|
||||
**Solution**: Check `<script src="https://cdn.tailwindcss.com"></script>` in `<head>`
|
||||
|
||||
### Issue: Navigation not working
|
||||
**Solution**: Check relative paths (should be `[Page].html` from root)
|
||||
|
||||
### Issue: Shared code not loading
|
||||
**Solution**: Check paths are `shared/[file].js`, `components/[file].js`
|
||||
|
||||
### Issue: Form not submitting
|
||||
**Solution**: Check `event.preventDefault()` in `handleSubmit(event)`
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
**Work Files** (`work/` folder):
|
||||
- High-level plans for each page
|
||||
- Section breakdowns
|
||||
- Object ID maps
|
||||
- Acceptance criteria
|
||||
|
||||
**Story Files** (`stories/` folder):
|
||||
- Detailed section implementation guides
|
||||
- Created just-in-time during development
|
||||
- Document what was actually built
|
||||
- Include changes from original plan
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Production Migration
|
||||
|
||||
### Steps to Production
|
||||
1. **Replace** `PrototypeAPI` calls with real backend
|
||||
2. **Migrate** sessionStorage to database
|
||||
3. **Add** authentication layer
|
||||
4. **Implement** proper error handling
|
||||
5. **Add** loading states for real network delays
|
||||
6. **Setup** Tailwind build process (vs CDN)
|
||||
7. **Optimize** images and assets
|
||||
8. **Test** with real data
|
||||
|
||||
### Migration Helpers
|
||||
- Search for `TODO:` comments in code
|
||||
- Check `PrototypeAPI` methods for Supabase equivalents
|
||||
- Review work files for production requirements
|
||||
|
||||
---
|
||||
|
||||
## 📧 Support & Questions
|
||||
|
||||
**For design questions**: Review story files in `stories/` folder
|
||||
**For functionality questions**: Review work files in `work/` folder
|
||||
**For implementation details**: Check inline comments in HTML files
|
||||
**For API questions**: Review `shared/prototype-api.js` documentation
|
||||
|
||||
---
|
||||
|
||||
## 📊 Scenario Statistics
|
||||
|
||||
**Total Pages**: [N]
|
||||
**Completed**: [N]
|
||||
**In Progress**: [N]
|
||||
**Total Sections**: [N]
|
||||
**Object IDs**: [N]
|
||||
**Shared Components**: [N]
|
||||
**Demo Data Files**: [N]
|
||||
|
||||
**Estimated Test Time**: [X] minutes (complete flow)
|
||||
**Estimated Build Time**: [X] hours (all pages)
|
||||
|
||||
---
|
||||
|
||||
## 📝 Change Log
|
||||
|
||||
### [Date]
|
||||
- [Change description]
|
||||
- [Page affected]
|
||||
|
||||
### [Date]
|
||||
- [Change description]
|
||||
- [Page affected]
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: [Date]
|
||||
**Version**: 1.0
|
||||
**Status**: [In Development | Testing | Complete]
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
# Dev Mode - Usage Guide
|
||||
|
||||
**Purpose**: Easy feedback on prototypes by copying Object IDs to clipboard
|
||||
|
||||
---
|
||||
|
||||
## 🎯 What is Dev Mode?
|
||||
|
||||
Dev Mode is a built-in feature in all WDS prototypes that allows testers, stakeholders, and designers to easily reference specific UI elements when providing feedback.
|
||||
|
||||
Instead of saying *"The button in the top right"*, you can say *"Fix `customer-sign-bankid`"* - precise and unambiguous!
|
||||
|
||||
---
|
||||
|
||||
## 🚀 How to Use
|
||||
|
||||
### Step 1: Activate Dev Mode
|
||||
|
||||
**Two ways**:
|
||||
1. Click the **Dev Mode button** (top-right corner)
|
||||
2. Press **Ctrl+E** on your keyboard
|
||||
|
||||
The button will turn blue and say **"Dev Mode: ON"**
|
||||
|
||||
---
|
||||
|
||||
### Step 2: Find the Element
|
||||
|
||||
- **Hover** over any element you want to reference
|
||||
- You'll see a **gray outline** appear
|
||||
- A **tooltip** shows the Object ID
|
||||
|
||||
**Prototype still works normally!** You can click buttons, fill forms, etc.
|
||||
|
||||
---
|
||||
|
||||
### Step 3: Copy the Object ID
|
||||
|
||||
- **Hold the Shift key** (outline turns **green**)
|
||||
- **Click the element** while holding Shift
|
||||
- **Object ID is copied!** ✓
|
||||
|
||||
You'll see a green success message: **"✓ Copied: [object-id]"**
|
||||
|
||||
**Important**: Shift key is **disabled when typing in form fields** (input, textarea, etc.) so you can type capital letters and special characters normally!
|
||||
|
||||
---
|
||||
|
||||
### Step 4: Paste in Feedback
|
||||
|
||||
Now paste the Object ID in your feedback:
|
||||
|
||||
**Good feedback**:
|
||||
```
|
||||
❌ Issue with `customer-sign-bankid`:
|
||||
The button is disabled even after I check the consent checkbox.
|
||||
|
||||
💡 Suggestion for `sidebar-video`:
|
||||
Make the video auto-play on mobile.
|
||||
```
|
||||
|
||||
**Developer knows EXACTLY** which element you're talking about!
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Visual Guide
|
||||
|
||||
| State | Appearance | Action |
|
||||
|-------|------------|--------|
|
||||
| **Dev Mode OFF** | Normal prototype | Click button or press Ctrl+E |
|
||||
| **Dev Mode ON (hovering)** | Gray outline | Shows Object ID in tooltip |
|
||||
| **Shift held (hovering)** | Green outline | Click to copy |
|
||||
| **After copying** | Green flash | Object ID in clipboard |
|
||||
|
||||
---
|
||||
|
||||
## ⌨️ Keyboard Shortcuts
|
||||
|
||||
- **Ctrl+E**: Toggle Dev Mode on/off
|
||||
- **Shift + Click**: Copy Object ID (when dev mode is on)
|
||||
|
||||
---
|
||||
|
||||
## 💡 Tips
|
||||
|
||||
1. **Activate once**, then navigate through prototype normally
|
||||
2. **Hold Shift only when copying** - prototype works without it
|
||||
3. **Type in fields normally** - Shift is disabled when focused on input/textarea
|
||||
4. **Deactivate when done** testing (Ctrl+E again)
|
||||
5. **Object IDs are permanent** - always refer to the same element
|
||||
|
||||
---
|
||||
|
||||
## 📋 Example Workflow
|
||||
|
||||
### Tester's Perspective:
|
||||
|
||||
1. Open prototype
|
||||
2. Press **Ctrl+E** (Dev Mode on)
|
||||
3. Test the prototype normally
|
||||
4. Find a bug - hover over problem element
|
||||
5. Hold **Shift**, click element
|
||||
6. Paste Object ID into bug report: "`customer-facility-startdate-group` shows wrong default date"
|
||||
7. Continue testing
|
||||
|
||||
### Designer's Perspective:
|
||||
|
||||
Receives feedback:
|
||||
```
|
||||
Bug: `customer-facility-startdate-group` shows wrong default date
|
||||
```
|
||||
|
||||
- Open prototype
|
||||
- Press **Ctrl+F** in browser, search for `customer-facility-startdate-group`
|
||||
- Find exact element in code
|
||||
- Fix the date calculation
|
||||
- Done! ✅
|
||||
|
||||
---
|
||||
|
||||
## 🔧 For Developers
|
||||
|
||||
When you receive Object IDs in feedback:
|
||||
|
||||
1. Open the HTML file
|
||||
2. Search for the Object ID (Ctrl+F)
|
||||
3. Element is either:
|
||||
- `id="object-id"` attribute
|
||||
- `data-object-id="object-id"` attribute
|
||||
4. Fix the issue in that specific element
|
||||
|
||||
---
|
||||
|
||||
## ❓ FAQs
|
||||
|
||||
**Q: Does Dev Mode affect the prototype?**
|
||||
A: No! The prototype works normally. You need to hold Shift to copy IDs.
|
||||
|
||||
**Q: Can I use this on mobile?**
|
||||
A: Yes! The button appears on mobile too. Use a Bluetooth keyboard or on-screen Shift key.
|
||||
|
||||
**Q: Can I type in form fields while Dev Mode is on?**
|
||||
A: Yes! Shift key is automatically disabled when you're typing in input fields or textareas, so you can type capital letters and special characters normally.
|
||||
|
||||
**Q: What if an element doesn't have an ID?**
|
||||
A: Dev Mode walks up the tree to find the nearest parent with an ID.
|
||||
|
||||
**Q: Can I copy multiple IDs?**
|
||||
A: Yes! Hold Shift, click first element, release Shift, hold again, click second element, etc.
|
||||
|
||||
**Q: Is this only for bugs?**
|
||||
A: No! Use it for any feedback - bugs, suggestions, questions, clarifications.
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Best Practices
|
||||
|
||||
### For Testers:
|
||||
- ✅ **DO**: Include Object ID in every piece of feedback
|
||||
- ✅ **DO**: Test prototype normally, copy IDs when needed
|
||||
- ✅ **DO**: Combine Object ID with description
|
||||
- ❌ **DON'T**: Leave Dev Mode on during normal use
|
||||
|
||||
### For Designers:
|
||||
- ✅ **DO**: Ensure all interactive elements have Object IDs
|
||||
- ✅ **DO**: Use descriptive, consistent naming
|
||||
- ✅ **DO**: Include Dev Mode in all prototypes
|
||||
- ❌ **DON'T**: Change Object IDs after sharing prototype
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Troubleshooting
|
||||
|
||||
**Problem**: Dev Mode button not showing
|
||||
**Solution**: Check that `dev-mode.js` and `dev-mode.css` are loaded
|
||||
|
||||
**Problem**: Clicking doesn't copy
|
||||
**Solution**: Make sure you're holding **Shift** while clicking
|
||||
|
||||
**Problem**: Tooltip not showing
|
||||
**Solution**: Element might not have an ID - check console logs
|
||||
|
||||
**Problem**: Can't turn off Dev Mode
|
||||
**Solution**: Press Ctrl+E or refresh the page
|
||||
|
||||
---
|
||||
|
||||
**Dev Mode makes feedback precise, fast, and frustration-free!** 🎯
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
/* ============================================================================
|
||||
PROTOTYPE DEV MODE STYLES
|
||||
|
||||
Styles for developer/feedback mode that allows copying Object IDs
|
||||
|
||||
Usage: Include these styles in your prototype HTML or CSS file
|
||||
============================================================================ */
|
||||
|
||||
/* Dev Mode Toggle Button */
|
||||
.dev-mode-toggle {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 9999;
|
||||
background: #fff;
|
||||
border: 2px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
padding: 10px 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.dev-mode-toggle:hover {
|
||||
background: #f9fafb;
|
||||
border-color: #d1d5db;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.dev-mode-toggle.active {
|
||||
background: #0066CC;
|
||||
border-color: #0066CC;
|
||||
color: #fff;
|
||||
box-shadow: 0 4px 12px rgba(0, 102, 204, 0.3);
|
||||
}
|
||||
|
||||
.dev-mode-toggle svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Dev Mode Active State */
|
||||
body.dev-mode-active {
|
||||
cursor: help !important; /* Show help cursor to indicate special mode */
|
||||
}
|
||||
|
||||
/* Subtle element highlighting on hover (not Shift held) */
|
||||
body.dev-mode-active [id]:hover {
|
||||
outline: 2px solid #6b7280 !important;
|
||||
outline-offset: 2px !important;
|
||||
box-shadow: 0 0 0 2px rgba(107, 114, 128, 0.2) !important;
|
||||
}
|
||||
|
||||
/* Active highlighting when Shift is held (ready to copy) */
|
||||
body.dev-mode-active.shift-held {
|
||||
cursor: copy !important;
|
||||
}
|
||||
|
||||
body.dev-mode-active.shift-held [id]:hover {
|
||||
outline: 3px solid #10B981 !important;
|
||||
outline-offset: 3px !important;
|
||||
box-shadow: 0 0 0 5px rgba(16, 185, 129, 0.4) !important;
|
||||
}
|
||||
|
||||
/* Dev Mode Tooltip */
|
||||
.dev-mode-tooltip {
|
||||
position: fixed;
|
||||
background: #1F2937;
|
||||
color: #fff;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
font-family: 'Courier New', monospace;
|
||||
z-index: 10000;
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.dev-mode-tooltip::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
left: 8px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: inherit;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
/* Disable only certain interactions when Shift is held in dev mode */
|
||||
body.dev-mode-active.shift-held button:not(#dev-mode-toggle),
|
||||
body.dev-mode-active.shift-held input,
|
||||
body.dev-mode-active.shift-held select,
|
||||
body.dev-mode-active.shift-held textarea,
|
||||
body.dev-mode-active.shift-held a {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
/* But allow the toggle button to work */
|
||||
body.dev-mode-active #dev-mode-toggle,
|
||||
body.dev-mode-active #dev-mode-toggle * {
|
||||
pointer-events: auto !important;
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
/* Feedback overlay (created dynamically) */
|
||||
@keyframes fadeInOut {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -50%) scale(0.9);
|
||||
}
|
||||
20% {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
80% {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -50%) scale(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive: Adjust toggle button on mobile */
|
||||
@media (max-width: 768px) {
|
||||
.dev-mode-toggle {
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
padding: 8px 12px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.dev-mode-toggle span {
|
||||
display: none; /* Hide text on mobile, show only icon */
|
||||
}
|
||||
|
||||
.dev-mode-toggle.active span {
|
||||
display: inline; /* Show "ON" status */
|
||||
max-width: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Optional: Add visual indicator when Shift is held */
|
||||
body.dev-mode-active.shift-held .dev-mode-toggle::after {
|
||||
content: '⬆️';
|
||||
margin-left: 4px;
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; transform: scale(1); }
|
||||
50% { opacity: 0.7; transform: scale(1.1); }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<!-- ============================================================================
|
||||
PROTOTYPE DEV MODE - HTML SNIPPET
|
||||
|
||||
Add this HTML to your prototype page (inside <body>, preferably at the top)
|
||||
============================================================================ -->
|
||||
|
||||
<!-- Dev Mode Toggle Button (fixed position, top-right) -->
|
||||
<button id="dev-mode-toggle" class="dev-mode-toggle" title="Toggle Dev Mode (Ctrl+E)">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
|
||||
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
|
||||
</svg>
|
||||
<span>Dev Mode: OFF</span>
|
||||
</button>
|
||||
|
||||
<!-- Dev Mode Tooltip (shown when hovering over elements in dev mode) -->
|
||||
<div id="dev-mode-tooltip" class="dev-mode-tooltip" style="display: none;"></div>
|
||||
|
||||
@@ -0,0 +1,430 @@
|
||||
/* eslint-disable n/no-unsupported-features/node-builtins */
|
||||
/* global document, window */
|
||||
|
||||
/**
|
||||
* PROTOTYPE DEV MODE
|
||||
*
|
||||
* Developer/feedback mode that allows users to easily copy Object IDs to clipboard
|
||||
* for providing precise feedback on prototype elements.
|
||||
*
|
||||
* Features:
|
||||
* - Toggle dev mode with button or Ctrl+E
|
||||
* - Prototype works NORMALLY when dev mode is on
|
||||
* - Hold Shift + Click any element to copy its Object ID
|
||||
* - Visual highlights show what will be copied (green when Shift is held)
|
||||
* - Tooltip shows Object ID on hover
|
||||
* - Success feedback when copied
|
||||
*
|
||||
* Usage:
|
||||
* 1. Include this script in your prototype HTML
|
||||
* 2. Add the HTML toggle button and tooltip (see HTML template)
|
||||
* 3. Add the CSS styles (see CSS template)
|
||||
* 4. Call initDevMode() on page load
|
||||
*
|
||||
* How it works:
|
||||
* - Activate dev mode (Ctrl+E or click button)
|
||||
* - Hover over elements to see their Object IDs (gray outline)
|
||||
* - Hold Shift key (outline turns green)
|
||||
* - Click while holding Shift to copy Object ID
|
||||
* - Prototype works normally without Shift held
|
||||
* - **Shift is disabled when typing in form fields** (input, textarea, etc.)
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// DEV MODE STATE
|
||||
// ============================================================================
|
||||
|
||||
let devModeActive = false;
|
||||
let shiftKeyPressed = false;
|
||||
let currentHighlightedElement = null;
|
||||
|
||||
// ============================================================================
|
||||
// INITIALIZATION
|
||||
// ============================================================================
|
||||
|
||||
function initDevMode() {
|
||||
const toggleButton = document.querySelector('#dev-mode-toggle');
|
||||
const tooltip = document.querySelector('#dev-mode-tooltip');
|
||||
|
||||
if (!toggleButton || !tooltip) {
|
||||
console.warn('⚠️ Dev Mode: Toggle button or tooltip not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if user agent supports clipboard API
|
||||
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
||||
// Clipboard API available
|
||||
} else {
|
||||
console.warn('⚠️ Clipboard API not supported in this browser');
|
||||
return;
|
||||
}
|
||||
|
||||
setupKeyboardShortcuts();
|
||||
setupToggleButton(toggleButton, tooltip);
|
||||
setupHoverHighlight(tooltip);
|
||||
setupClickCopy();
|
||||
|
||||
console.log('%c💡 Dev Mode available: Press Ctrl+E or click the Dev Mode button', 'color: #0066CC; font-weight: bold;');
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// KEYBOARD SHORTCUTS
|
||||
// ============================================================================
|
||||
|
||||
function setupKeyboardShortcuts() {
|
||||
// Track Shift key for container selection
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Shift') {
|
||||
// Don't activate if user is typing in a form field
|
||||
if (isTypingInField()) {
|
||||
return;
|
||||
}
|
||||
|
||||
shiftKeyPressed = true;
|
||||
document.body.classList.add('shift-held');
|
||||
if (devModeActive) {
|
||||
console.log('%c⬆️ Shift held: Click any element to copy its Object ID', 'color: #10B981; font-weight: bold;');
|
||||
}
|
||||
}
|
||||
|
||||
// Ctrl+E toggle
|
||||
if (e.ctrlKey && e.key === 'e') {
|
||||
e.preventDefault();
|
||||
document.querySelector('#dev-mode-toggle')?.click();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keyup', (e) => {
|
||||
if (e.key === 'Shift') {
|
||||
shiftKeyPressed = false;
|
||||
document.body.classList.remove('shift-held');
|
||||
if (devModeActive) {
|
||||
console.log('%c⬇️ Shift released: Prototype works normally (hold Shift to copy)', 'color: #6b7280;');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TOGGLE BUTTON
|
||||
// ============================================================================
|
||||
|
||||
function setupToggleButton(toggleButton, tooltip) {
|
||||
toggleButton.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
if (typeof globalThis !== 'undefined') {
|
||||
globalThis.devModeActive = true;
|
||||
} else if (globalThis.window !== undefined) {
|
||||
globalThis.devModeActive = true;
|
||||
}
|
||||
devModeActive = !devModeActive;
|
||||
|
||||
// Update UI
|
||||
document.body.classList.toggle('dev-mode-active', devModeActive);
|
||||
toggleButton.classList.toggle('active', devModeActive);
|
||||
|
||||
const statusText = toggleButton.querySelector('span');
|
||||
if (statusText) {
|
||||
statusText.textContent = devModeActive ? 'Dev Mode: ON' : 'Dev Mode: OFF';
|
||||
}
|
||||
|
||||
// Log status
|
||||
console.log(`🔧 Dev Mode: ${devModeActive ? 'ACTIVATED' : 'DEACTIVATED'}`);
|
||||
|
||||
if (devModeActive) {
|
||||
console.log('%c🔧 DEV MODE ACTIVE', 'color: #0066CC; font-size: 16px; font-weight: bold;');
|
||||
console.log('%c⚠️ Hold SHIFT + Click any element to copy its Object ID', 'color: #FFB800; font-size: 14px; font-weight: bold;');
|
||||
console.log('%cWithout Shift: Prototype works normally', 'color: #6b7280;');
|
||||
console.log('%cPress Ctrl+E to toggle Dev Mode', 'color: #6b7280;');
|
||||
} else {
|
||||
tooltip.style.display = 'none';
|
||||
if (currentHighlightedElement) {
|
||||
clearHighlight();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HOVER HIGHLIGHT
|
||||
// ============================================================================
|
||||
|
||||
function setupHoverHighlight(tooltip) {
|
||||
// Show tooltip and highlight on hover
|
||||
document.addEventListener('mouseover', function (e) {
|
||||
if (!devModeActive) return;
|
||||
|
||||
// Don't highlight if user is typing in a field
|
||||
if (isTypingInField()) {
|
||||
tooltip.style.display = 'none';
|
||||
clearHighlight();
|
||||
return;
|
||||
}
|
||||
|
||||
clearHighlight();
|
||||
|
||||
let element = findElementWithId(e.target);
|
||||
|
||||
if (!element || !element.id || isSystemElement(element.id)) {
|
||||
tooltip.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
// Highlight element
|
||||
highlightElement(element, shiftKeyPressed);
|
||||
currentHighlightedElement = element;
|
||||
|
||||
// Show tooltip
|
||||
const prefix = shiftKeyPressed ? '✓ Click to Copy: ' : '⬆️ Hold Shift + Click: ';
|
||||
tooltip.textContent = prefix + element.id;
|
||||
tooltip.style.display = 'block';
|
||||
tooltip.style.background = shiftKeyPressed ? '#10B981' : '#6b7280';
|
||||
tooltip.style.color = '#fff';
|
||||
|
||||
updateTooltipPosition(e, tooltip);
|
||||
});
|
||||
|
||||
// Update tooltip position on mouse move
|
||||
document.addEventListener('mousemove', function (e) {
|
||||
if (devModeActive && tooltip.style.display === 'block') {
|
||||
updateTooltipPosition(e, tooltip);
|
||||
}
|
||||
});
|
||||
|
||||
// Clear highlight on mouse out
|
||||
document.addEventListener('mouseout', function (e) {
|
||||
if (!devModeActive) return;
|
||||
if (e.target.id) {
|
||||
tooltip.style.display = 'none';
|
||||
clearHighlight();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CLICK TO COPY
|
||||
// ============================================================================
|
||||
|
||||
function setupClickCopy() {
|
||||
// Use capture phase to intercept clicks with Shift
|
||||
document.addEventListener(
|
||||
'click',
|
||||
function (e) {
|
||||
if (!devModeActive) return;
|
||||
|
||||
// Allow toggle button to work normally
|
||||
if (isToggleButton(e.target)) return;
|
||||
|
||||
// ONLY copy if Shift is held
|
||||
if (!shiftKeyPressed) {
|
||||
// Let prototype work normally without Shift
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't intercept if user is clicking in/around a form field
|
||||
if (isTypingInField() || isFormElement(e.target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Shift is held and not in a form field - intercept and copy
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
let element = findElementWithId(e.target);
|
||||
|
||||
if (!element || !element.id || isSystemElement(element.id)) {
|
||||
console.log('❌ No Object ID found');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy to clipboard
|
||||
const objectId = element.id;
|
||||
copyToClipboard(objectId);
|
||||
|
||||
// Show feedback
|
||||
showCopyFeedback(element, objectId);
|
||||
|
||||
return false;
|
||||
},
|
||||
true,
|
||||
); // Capture phase
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
function findElementWithId(element) {
|
||||
let current = element;
|
||||
let attempts = 0;
|
||||
|
||||
while (current && !current.id && attempts < 10) {
|
||||
current = current.parentElement;
|
||||
attempts++;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
function isSystemElement(id) {
|
||||
const systemIds = ['app', 'dev-mode-toggle', 'dev-mode-tooltip'];
|
||||
return systemIds.includes(id);
|
||||
}
|
||||
|
||||
function isToggleButton(element) {
|
||||
return element.id === 'dev-mode-toggle' || element.closest('#dev-mode-toggle') || element.classList.contains('dev-mode-toggle');
|
||||
}
|
||||
|
||||
function isTypingInField() {
|
||||
const activeElement = document.activeElement;
|
||||
if (!activeElement) return false;
|
||||
|
||||
const tagName = activeElement.tagName.toLowerCase();
|
||||
const isEditable = activeElement.isContentEditable;
|
||||
|
||||
// Check if user is currently typing in a form field
|
||||
return tagName === 'input' || tagName === 'textarea' || tagName === 'select' || isEditable;
|
||||
}
|
||||
|
||||
function isFormElement(element) {
|
||||
if (!element) return false;
|
||||
|
||||
const tagName = element.tagName.toLowerCase();
|
||||
const isEditable = element.isContentEditable;
|
||||
|
||||
// Check if the clicked element is a form element
|
||||
return tagName === 'input' || tagName === 'textarea' || tagName === 'select' || isEditable;
|
||||
}
|
||||
|
||||
function highlightElement(element, isShiftHeld) {
|
||||
const color = isShiftHeld ? '#10B981' : '#6b7280';
|
||||
const width = isShiftHeld ? '3px' : '2px';
|
||||
const offset = isShiftHeld ? '3px' : '2px';
|
||||
const shadowSpread = isShiftHeld ? '5px' : '2px';
|
||||
const shadowOpacity = isShiftHeld ? '0.4' : '0.2';
|
||||
|
||||
element.style.outline = `${width} solid ${color}`;
|
||||
element.style.outlineOffset = offset;
|
||||
element.style.boxShadow = `0 0 0 ${shadowSpread} rgba(${isShiftHeld ? '16, 185, 129' : '107, 114, 128'}, ${shadowOpacity})`;
|
||||
}
|
||||
|
||||
function clearHighlight() {
|
||||
if (currentHighlightedElement) {
|
||||
currentHighlightedElement.style.outline = '';
|
||||
currentHighlightedElement.style.boxShadow = '';
|
||||
currentHighlightedElement = null;
|
||||
}
|
||||
}
|
||||
|
||||
function updateTooltipPosition(e, tooltip) {
|
||||
const offset = 15;
|
||||
let x = e.clientX + offset;
|
||||
let y = e.clientY + offset;
|
||||
|
||||
// Keep tooltip on screen
|
||||
const rect = tooltip.getBoundingClientRect();
|
||||
if (x + rect.width > window.innerWidth) {
|
||||
x = e.clientX - rect.width - offset;
|
||||
}
|
||||
if (y + rect.height > window.innerHeight) {
|
||||
y = e.clientY - rect.height - offset;
|
||||
}
|
||||
|
||||
tooltip.style.left = x + 'px';
|
||||
tooltip.style.top = y + 'px';
|
||||
}
|
||||
|
||||
function copyToClipboard(text) {
|
||||
if (typeof navigator !== 'undefined' && navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard
|
||||
.writeText(text)
|
||||
.then(() => {
|
||||
console.log(`📋 Copied to clipboard: ${text}`);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Dev Mode error:', error);
|
||||
fallbackCopy(text);
|
||||
});
|
||||
} else {
|
||||
fallbackCopy(text);
|
||||
}
|
||||
}
|
||||
|
||||
function fallbackCopy(text) {
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
textarea.style.position = 'fixed';
|
||||
textarea.style.left = '-999999px';
|
||||
document.body.append(textarea);
|
||||
textarea.focus();
|
||||
textarea.select();
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
console.log(`📋 Copied (fallback): ${text}`);
|
||||
} catch (error) {
|
||||
console.error('Dev Mode error:', error);
|
||||
}
|
||||
|
||||
textarea.remove();
|
||||
}
|
||||
|
||||
function showCopyFeedback(element, objectId) {
|
||||
// Create feedback overlay
|
||||
const feedback = document.createElement('div');
|
||||
feedback.textContent = '✓ Copied: ' + objectId;
|
||||
feedback.style.cssText = `
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: #10B981;
|
||||
color: #fff;
|
||||
padding: 16px 32px;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
z-index: 100000;
|
||||
box-shadow: 0 10px 25px rgba(0,0,0,0.3);
|
||||
animation: fadeInOut 1.5s ease-in-out;
|
||||
pointer-events: none;
|
||||
`;
|
||||
|
||||
document.body.append(feedback);
|
||||
|
||||
setTimeout(() => {
|
||||
feedback.remove();
|
||||
}, 1500);
|
||||
|
||||
// Flash element
|
||||
const originalOutline = element.style.outline;
|
||||
element.style.outline = '3px solid #10B981';
|
||||
setTimeout(() => {
|
||||
element.style.outline = originalOutline;
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// Add CSS animation
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes fadeInOut {
|
||||
0% { opacity: 0; transform: translate(-50%, -50%) scale(0.9); }
|
||||
20% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
||||
80% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
||||
100% { opacity: 0; transform: translate(-50%, -50%) scale(0.9); }
|
||||
}
|
||||
`;
|
||||
document.head.append(style);
|
||||
|
||||
// ============================================================================
|
||||
// EXPORT
|
||||
// ============================================================================
|
||||
|
||||
// Make available globally
|
||||
globalThis.initDevMode = initDevMode;
|
||||
|
||||
// Export for use in other scripts
|
||||
if (typeof globalThis !== 'undefined' && globalThis.exports) {
|
||||
globalThis.exports = { initDevMode };
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"user": {
|
||||
"id": "demo-user-001",
|
||||
"firstName": "[First Name]",
|
||||
"lastName": "[Last Name]",
|
||||
"email": "[email@example.com]",
|
||||
"phoneNumber": "[+1234567890]",
|
||||
"picture": "",
|
||||
"role": "owner",
|
||||
"createdAt": "2024-01-01T00:00:00.000Z",
|
||||
"updatedAt": "2024-01-01T00:00:00.000Z"
|
||||
},
|
||||
"family": {
|
||||
"id": "demo-family-001",
|
||||
"name": "[Family Name]",
|
||||
"description": "[Brief family description]",
|
||||
"location": "[City, Country]",
|
||||
"picture": "",
|
||||
"ownerId": "demo-user-001",
|
||||
"createdAt": "2024-01-01T00:00:00.000Z",
|
||||
"updatedAt": "2024-01-01T00:00:00.000Z"
|
||||
},
|
||||
"members": [
|
||||
{
|
||||
"id": "demo-member-001",
|
||||
"familyId": "demo-family-001",
|
||||
"userId": "demo-user-001",
|
||||
"firstName": "[Member 1 First Name]",
|
||||
"lastName": "[Member 1 Last Name]",
|
||||
"email": "[member1@example.com]",
|
||||
"role": "owner",
|
||||
"picture": "",
|
||||
"createdAt": "2024-01-01T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"id": "demo-member-002",
|
||||
"familyId": "demo-family-001",
|
||||
"userId": "demo-user-002",
|
||||
"firstName": "[Member 2 First Name]",
|
||||
"lastName": "[Member 2 Last Name]",
|
||||
"email": "[member2@example.com]",
|
||||
"role": "co-owner",
|
||||
"picture": "",
|
||||
"createdAt": "2024-01-02T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"dogs": [
|
||||
{
|
||||
"id": "demo-dog-001",
|
||||
"familyId": "demo-family-001",
|
||||
"name": "[Dog Name]",
|
||||
"breed": "[Dog Breed]",
|
||||
"gender": "male",
|
||||
"birthDate": "2020-05-15",
|
||||
"color": "[Color]",
|
||||
"specialNeeds": "[Any special needs or notes]",
|
||||
"picture": "",
|
||||
"createdAt": "2024-01-01T00:00:00.000Z",
|
||||
"updatedAt": "2024-01-01T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"comment": "This is demo data that loads automatically when prototype is opened for the first time. Edit this file to change the demo data. All fields with empty strings ('') are optional."
|
||||
}
|
||||
@@ -0,0 +1,465 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="se">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>[Page-Number] [Page Name] - [Project Name]</title>
|
||||
|
||||
<!-- Tailwind CSS via CDN -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
<!-- Tailwind Config (Design Tokens) -->
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'[project-name]': {
|
||||
50: '#eff6ff',
|
||||
100: '#dbeafe',
|
||||
500: '#2563eb',
|
||||
600: '#1d4ed8',
|
||||
700: '#1e40af',
|
||||
}
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', 'sans-serif'],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Google Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Dev Mode Styles (feedback/testing tool) -->
|
||||
<link rel="stylesheet" href="components/dev-mode.css">
|
||||
|
||||
<!-- Custom Styles (minimal - only what Tailwind can't do) -->
|
||||
<style>
|
||||
/* Custom styles that can't be done with Tailwind */
|
||||
/* Example: Complex animations, special overlays, etc. */
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-h-screen bg-gray-50 font-sans">
|
||||
<!-- ========================================================================
|
||||
DEV MODE TOGGLE (for easy feedback - copy Object IDs)
|
||||
======================================================================== -->
|
||||
<button id="dev-mode-toggle" class="dev-mode-toggle" title="Toggle Dev Mode (Ctrl+E)">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
|
||||
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
|
||||
</svg>
|
||||
<span>Dev Mode: OFF</span>
|
||||
</button>
|
||||
<div id="dev-mode-tooltip" class="dev-mode-tooltip" style="display: none;"></div>
|
||||
<!-- ========================================================================
|
||||
HEADER
|
||||
======================================================================== -->
|
||||
<header class="bg-white border-b border-gray-200 px-4 py-3 flex items-center justify-between">
|
||||
<!-- Back Button -->
|
||||
<button
|
||||
id="[page]-header-back"
|
||||
data-object-id="[page]-header-back"
|
||||
onclick="history.back()"
|
||||
class="text-gray-600 hover:text-gray-900 font-medium text-sm transition-colors"
|
||||
>
|
||||
← [Back Text]
|
||||
</button>
|
||||
|
||||
<!-- Page Title -->
|
||||
<h1
|
||||
id="[page]-header-title"
|
||||
data-object-id="[page]-header-title"
|
||||
class="text-lg font-semibold text-gray-900"
|
||||
>
|
||||
[Page Title]
|
||||
</h1>
|
||||
|
||||
<!-- Spacer (for alignment) -->
|
||||
<div class="w-20"></div>
|
||||
|
||||
<!-- Optional: Language Selector or Action Button -->
|
||||
<!-- <button class="text-[project-name]-600">Action</button> -->
|
||||
</header>
|
||||
|
||||
<!-- ========================================================================
|
||||
MAIN CONTENT
|
||||
======================================================================== -->
|
||||
<main class="max-w-md mx-auto p-4">
|
||||
<form id="[page]Form" class="space-y-4" onsubmit="handleSubmit(event)">
|
||||
|
||||
<!-- ============================================================
|
||||
SECTION 1: Example - Picture Upload
|
||||
============================================================ -->
|
||||
<div class="flex items-center gap-4 mb-6">
|
||||
<button
|
||||
type="button"
|
||||
id="[page]-picture-upload"
|
||||
data-object-id="[page]-picture-upload"
|
||||
onclick="handlePictureUpload()"
|
||||
class="w-24 h-24 rounded-full bg-gray-100 border-2 border-dashed border-gray-300 flex items-center justify-center hover:border-[project-name]-500 hover:bg-gray-50 transition-all cursor-pointer"
|
||||
>
|
||||
<img id="picturePreview" class="hidden w-full h-full rounded-full object-cover" alt="Preview" />
|
||||
<svg class="w-10 h-10 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<input type="file" id="pictureInput" accept="image/*" class="hidden">
|
||||
|
||||
<div class="flex-1">
|
||||
<label class="text-sm text-gray-700 font-medium">
|
||||
[Upload Label]
|
||||
</label>
|
||||
<p class="text-xs text-red-600 hidden" id="pictureError"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============================================================
|
||||
SECTION 2: Example - Text Input
|
||||
============================================================ -->
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
id="[page]-input-[field]"
|
||||
data-object-id="[page]-input-[field]"
|
||||
name="[fieldName]"
|
||||
placeholder="[Placeholder] *"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[project-name]-500 focus:border-transparent transition-all"
|
||||
required
|
||||
/>
|
||||
<p class="text-sm text-red-600 hidden mt-1" id="[field]Error"></p>
|
||||
</div>
|
||||
|
||||
<!-- ============================================================
|
||||
SECTION 3: Example - Split Button (Binary Choice)
|
||||
============================================================ -->
|
||||
<div
|
||||
id="[page]-split-[choice]"
|
||||
data-object-id="[page]-split-[choice]"
|
||||
class="grid grid-cols-2 gap-0 border border-gray-300 rounded-lg overflow-hidden"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
id="choice1"
|
||||
onclick="selectChoice('option1')"
|
||||
class="py-2 text-center font-medium text-gray-700 hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
[Option 1]
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="choice2"
|
||||
onclick="selectChoice('option2')"
|
||||
class="py-2 text-center font-medium text-gray-700 hover:bg-gray-50 transition-colors border-l border-gray-300"
|
||||
>
|
||||
[Option 2]
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- ============================================================
|
||||
SECTION 4: Example - Textarea
|
||||
============================================================ -->
|
||||
<div>
|
||||
<textarea
|
||||
id="[page]-textarea-[field]"
|
||||
data-object-id="[page]-textarea-[field]"
|
||||
name="[fieldName]"
|
||||
placeholder="[Placeholder]"
|
||||
maxlength="500"
|
||||
rows="3"
|
||||
oninput="updateCharCounter()"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[project-name]-500 focus:border-transparent resize-none"
|
||||
></textarea>
|
||||
<p class="text-xs text-gray-500 text-right hidden" id="charCounter"></p>
|
||||
</div>
|
||||
|
||||
<!-- ============================================================
|
||||
SUBMIT BUTTON
|
||||
============================================================ -->
|
||||
<button
|
||||
type="submit"
|
||||
id="[page]-button-submit"
|
||||
data-object-id="[page]-button-submit"
|
||||
class="w-full py-3 bg-[project-name]-600 text-white rounded-lg font-semibold hover:bg-[project-name]-700 focus:outline-none focus:ring-2 focus:ring-[project-name]-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-all flex items-center justify-center gap-2"
|
||||
>
|
||||
<span id="submitButtonText">[Submit Text]</span>
|
||||
<svg id="submitButtonSpinner" class="hidden animate-spin w-5 h-5" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
<!-- ========================================================================
|
||||
SUCCESS TOAST
|
||||
======================================================================== -->
|
||||
<div id="successToast" class="hidden fixed bottom-6 left-1/2 -translate-x-1/2 bg-gray-900 text-white px-6 py-3 rounded-lg shadow-lg flex items-center gap-2 z-50 animate-slide-up">
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<span id="toastMessage">[Success Message]</span>
|
||||
</div>
|
||||
|
||||
<!-- ========================================================================
|
||||
ERROR BANNER (optional)
|
||||
======================================================================== -->
|
||||
<div id="errorBanner" class="hidden fixed top-20 left-1/2 -translate-x-1/2 max-w-md w-full mx-4 bg-red-50 border border-red-200 rounded-lg p-4 flex items-start gap-3 z-50">
|
||||
<svg class="w-5 h-5 text-red-600 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<div class="flex-1">
|
||||
<p class="font-medium text-red-900">Error</p>
|
||||
<p class="text-sm text-red-700" id="errorMessage">[Error message]</p>
|
||||
</div>
|
||||
<button onclick="hideErrorBanner()" class="text-red-600 hover:text-red-900">
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- ========================================================================
|
||||
MODALS (add as needed - example: image crop modal)
|
||||
======================================================================== -->
|
||||
<!-- Image Crop Modal Template (uncomment if using image upload) -->
|
||||
<!--
|
||||
<div id="cropModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
||||
<div class="bg-white rounded-lg max-w-md w-full overflow-hidden">
|
||||
<div class="flex items-center justify-between px-4 py-3 border-b border-gray-200">
|
||||
<button onclick="cancelCrop()" class="text-[project-name]-600 font-medium">Cancel</button>
|
||||
<h2 class="font-semibold text-gray-900">Crop Image</h2>
|
||||
<button onclick="replaceImage()" class="text-[project-name]-600 font-medium">Replace</button>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="relative bg-gray-100 rounded-lg overflow-hidden" style="height: 300px;">
|
||||
<img id="cropImage" src="" alt="Crop" class="w-full h-full object-contain">
|
||||
</div>
|
||||
<input type="range" id="zoomSlider" min="10" max="200" value="100"
|
||||
class="w-full mt-4 accent-[project-name]-600">
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<button onclick="confirmCrop()"
|
||||
class="w-full py-3 bg-[project-name]-600 text-white rounded-lg font-semibold hover:bg-[project-name]-700">
|
||||
Use Image
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<!-- ========================================================================
|
||||
JAVASCRIPT - Shared Scripts
|
||||
======================================================================== -->
|
||||
<script src="shared/prototype-api.js"></script>
|
||||
<script src="shared/init.js"></script>
|
||||
<script src="shared/utils.js"></script>
|
||||
|
||||
<!-- ========================================================================
|
||||
JAVASCRIPT - Dev Mode (feedback tool)
|
||||
======================================================================== -->
|
||||
<script src="components/dev-mode.js"></script>
|
||||
|
||||
<!-- ========================================================================
|
||||
JAVASCRIPT - Component Scripts (load as needed)
|
||||
======================================================================== -->
|
||||
<!-- <script src="components/image-crop.js"></script> -->
|
||||
<!-- <script src="components/toast.js"></script> -->
|
||||
<!-- <script src="components/modal.js"></script> -->
|
||||
<!-- <script src="components/form-validation.js"></script> -->
|
||||
|
||||
<!-- ========================================================================
|
||||
JAVASCRIPT - Page-Specific Script (if complex logic)
|
||||
======================================================================== -->
|
||||
<!-- Option 1: External file (if >150 lines) -->
|
||||
<!-- <script src="pages/[page-number]-[page-name].js"></script> -->
|
||||
|
||||
<!-- Option 2: Inline script (preferred for <150 lines) -->
|
||||
<script>
|
||||
/**
|
||||
* Page: [Page Number] [Page Name]
|
||||
* Purpose: [Brief description]
|
||||
*/
|
||||
|
||||
// ================================================================
|
||||
// STATE MANAGEMENT
|
||||
// ================================================================
|
||||
let formData = {
|
||||
// Initialize form state
|
||||
};
|
||||
|
||||
// ================================================================
|
||||
// INITIALIZATION
|
||||
// ================================================================
|
||||
window.initPage = function() {
|
||||
console.log('📄 [Page Name] loaded');
|
||||
|
||||
// Page-specific initialization
|
||||
loadPageData();
|
||||
};
|
||||
|
||||
async function loadPageData() {
|
||||
try {
|
||||
// Load any required data
|
||||
const user = await window.PrototypeAPI.getUser();
|
||||
console.log('👤 Current user:', user);
|
||||
|
||||
// Pre-fill form if needed
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error loading data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// FORM HANDLING
|
||||
// ================================================================
|
||||
async function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
// Validate
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading
|
||||
setLoadingState(true);
|
||||
|
||||
try {
|
||||
// Collect form data
|
||||
const data = {
|
||||
// Extract form values
|
||||
};
|
||||
|
||||
// API call
|
||||
console.log('📤 Submitting:', data);
|
||||
const result = await window.PrototypeAPI.[method](data);
|
||||
console.log('✅ Success:', result);
|
||||
|
||||
// Show success
|
||||
showToast('[Success message]', 'success');
|
||||
|
||||
// Navigate (after delay)
|
||||
setTimeout(() => {
|
||||
window.location.href = '[next-page].html';
|
||||
}, 1500);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error:', error);
|
||||
showErrorBanner(error.message);
|
||||
} finally {
|
||||
setLoadingState(false);
|
||||
}
|
||||
}
|
||||
|
||||
function validateForm() {
|
||||
let isValid = true;
|
||||
|
||||
// Validate each field
|
||||
// Example:
|
||||
// const name = document.getElementById('[field]').value;
|
||||
// if (!name) {
|
||||
// showFieldError('[field]', 'This field is required');
|
||||
// isValid = false;
|
||||
// }
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// UI INTERACTIONS
|
||||
// ================================================================
|
||||
function handlePictureUpload() {
|
||||
document.getElementById('pictureInput').click();
|
||||
}
|
||||
|
||||
function selectChoice(choice) {
|
||||
// Handle choice selection
|
||||
console.log('Choice selected:', choice);
|
||||
}
|
||||
|
||||
function updateCharCounter() {
|
||||
const textarea = document.getElementById('[page]-textarea-[field]');
|
||||
const counter = document.getElementById('charCounter');
|
||||
const current = textarea.value.length;
|
||||
const max = textarea.maxLength;
|
||||
|
||||
counter.textContent = `${current}/${max}`;
|
||||
counter.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// FEEDBACK
|
||||
// ================================================================
|
||||
function setLoadingState(isLoading) {
|
||||
const btn = document.getElementById('[page]-button-submit');
|
||||
const text = document.getElementById('submitButtonText');
|
||||
const spinner = document.getElementById('submitButtonSpinner');
|
||||
|
||||
btn.disabled = isLoading;
|
||||
text.classList.toggle('hidden', isLoading);
|
||||
spinner.classList.toggle('hidden', !isLoading);
|
||||
}
|
||||
|
||||
function showToast(message, type = 'success') {
|
||||
const toast = document.getElementById('successToast');
|
||||
const messageEl = document.getElementById('toastMessage');
|
||||
|
||||
messageEl.textContent = message;
|
||||
toast.classList.remove('hidden');
|
||||
|
||||
setTimeout(() => {
|
||||
toast.classList.add('hidden');
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function showErrorBanner(message) {
|
||||
const banner = document.getElementById('errorBanner');
|
||||
const messageEl = document.getElementById('errorMessage');
|
||||
|
||||
messageEl.textContent = message;
|
||||
banner.classList.remove('hidden');
|
||||
|
||||
setTimeout(() => {
|
||||
banner.classList.add('hidden');
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
function hideErrorBanner() {
|
||||
document.getElementById('errorBanner').classList.add('hidden');
|
||||
}
|
||||
|
||||
function showFieldError(fieldId, message) {
|
||||
const errorEl = document.getElementById(`${fieldId}Error`);
|
||||
const inputEl = document.getElementById(fieldId);
|
||||
|
||||
if (errorEl) {
|
||||
errorEl.textContent = message;
|
||||
errorEl.classList.remove('hidden');
|
||||
}
|
||||
|
||||
if (inputEl) {
|
||||
inputEl.classList.add('border-red-500');
|
||||
}
|
||||
}
|
||||
|
||||
function clearFieldError(fieldId) {
|
||||
const errorEl = document.getElementById(`${fieldId}Error`);
|
||||
const inputEl = document.getElementById(fieldId);
|
||||
|
||||
if (errorEl) {
|
||||
errorEl.classList.add('hidden');
|
||||
}
|
||||
|
||||
if (inputEl) {
|
||||
inputEl.classList.remove('border-red-500');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
# Story [Page].[Section]: [Page Name] - [Section Name]
|
||||
|
||||
**Page**: [Page Number] [Page Name]
|
||||
**Section**: [N] of [Total]
|
||||
**Complexity**: Simple | Medium | Complex
|
||||
**Estimated Time**: [X] minutes
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Goal
|
||||
|
||||
[Brief description of what this section accomplishes]
|
||||
|
||||
---
|
||||
|
||||
## 📋 What to Build
|
||||
|
||||
### HTML Elements
|
||||
|
||||
```html
|
||||
<!-- [Description of HTML to add] -->
|
||||
<div class="[tailwind-classes]">
|
||||
<!-- Specific HTML structure here -->
|
||||
</div>
|
||||
```
|
||||
|
||||
### JavaScript (if needed)
|
||||
|
||||
```javascript
|
||||
// [Description of JavaScript functionality]
|
||||
function [functionName]() {
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
### Tailwind Classes to Use
|
||||
|
||||
**Key classes for this section**:
|
||||
- `[class-category]`: `[specific-classes]`
|
||||
- `[class-category]`: `[specific-classes]`
|
||||
|
||||
**Example combinations**:
|
||||
```html
|
||||
<!-- Input field -->
|
||||
<input class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[project]-500">
|
||||
|
||||
<!-- Button -->
|
||||
<button class="w-full py-3 bg-[project]-600 text-white rounded-lg font-semibold hover:bg-[project]-700 transition-colors">
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Dependencies
|
||||
|
||||
**Shared code**:
|
||||
- ✅ `shared/prototype-api.js` (already loaded)
|
||||
- ✅ `shared/init.js` (already loaded)
|
||||
|
||||
**Components** (load if not already included):
|
||||
- [ ] `components/image-crop.js` (if using image upload)
|
||||
- [ ] `components/toast.js` (if showing notifications)
|
||||
- [ ] `components/modal.js` (if using modals)
|
||||
- [ ] `components/form-validation.js` (if validating forms)
|
||||
|
||||
---
|
||||
|
||||
## 📸 Baseline State
|
||||
|
||||
_Capture with Puppeteer before implementation when modifying existing features. Skip for new sections._
|
||||
|
||||
| Element | Current Value | Notes |
|
||||
|---------|---------------|-------|
|
||||
| [element] | [current value] | [any relevant context] |
|
||||
|
||||
---
|
||||
|
||||
## 📝 Implementation Steps
|
||||
|
||||
### Step 1: [First Step]
|
||||
[What to do first]
|
||||
|
||||
### Step 2: [Second Step]
|
||||
[What to do second]
|
||||
|
||||
### Step 3: [Third Step]
|
||||
[What to do third]
|
||||
|
||||
---
|
||||
|
||||
## ✅ Acceptance Criteria
|
||||
|
||||
### Agent-Verifiable (Puppeteer)
|
||||
|
||||
| # | Criterion | Element | Expected | How to Verify |
|
||||
|---|-----------|---------|----------|---------------|
|
||||
| 1 | [Criterion] | `[selector]` | [Value] | [Method] |
|
||||
| 2 | [Criterion] | `[selector]` | [Value] | [Method] |
|
||||
| 3 | [Criterion] | `[selector]` | [Value] | [Method] |
|
||||
|
||||
### User-Evaluable (Qualitative)
|
||||
|
||||
- [ ] Flow feels natural and intuitive
|
||||
- [ ] Visual hierarchy guides the eye correctly
|
||||
- [ ] Section feels consistent with the rest of the prototype
|
||||
- [ ] [Additional qualitative criterion]
|
||||
|
||||
---
|
||||
|
||||
## 🧪 How to Test
|
||||
|
||||
### Puppeteer Self-Verification (Agent)
|
||||
|
||||
Before presenting to user:
|
||||
|
||||
1. Open `[Page-Number]-[Page-Name].html` in Puppeteer
|
||||
2. Set viewport to target width (375px for mobile)
|
||||
3. For each agent-verifiable criterion in the table above:
|
||||
- Locate element
|
||||
- Read actual value
|
||||
- Compare to expected
|
||||
- Narrate with ✓/✗
|
||||
4. Fix any mismatches and re-verify until all pass
|
||||
5. Check console for errors
|
||||
|
||||
See [Inline Testing Guide](../guides/INLINE-TESTING-GUIDE.md) for full methodology.
|
||||
|
||||
### User Qualitative Review
|
||||
|
||||
After Puppeteer verification passes, present to user:
|
||||
- Summarize Puppeteer results (X/Y criteria pass)
|
||||
- Ask user to evaluate qualitative criteria above
|
||||
- Collect feedback on feel, flow, clarity, consistency
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Common Issues & Fixes
|
||||
|
||||
### Issue: [Problem Description]
|
||||
**Symptom**: [What user sees]
|
||||
**Cause**: [Why it happens]
|
||||
**Fix**: [How to fix it]
|
||||
|
||||
### Issue: [Problem Description]
|
||||
**Symptom**: [What user sees]
|
||||
**Cause**: [Why it happens]
|
||||
**Fix**: [How to fix it]
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Design Notes
|
||||
|
||||
**Visual requirements**:
|
||||
- [Design consideration 1]
|
||||
- [Design consideration 2]
|
||||
|
||||
**UX considerations**:
|
||||
- [UX note 1]
|
||||
- [UX note 2]
|
||||
|
||||
---
|
||||
|
||||
## 💡 Tips
|
||||
|
||||
- [Helpful tip 1]
|
||||
- [Helpful tip 2]
|
||||
|
||||
---
|
||||
|
||||
## ➡️ Next Section
|
||||
|
||||
After this section is approved: `[Page].[NextSection]-[page-name]-[next-section-name].md`
|
||||
|
||||
---
|
||||
|
||||
## 📊 Status Tracking
|
||||
|
||||
**Status**: ⏸️ Not Started | 🚧 In Progress | ✅ Complete
|
||||
**Started**: [Date/Time]
|
||||
**Completed**: [Date/Time]
|
||||
**Approved By**: [Name]
|
||||
**Notes**: [Any special notes or changes made]
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Changes from Original Plan
|
||||
|
||||
*Document any deviations from the work file plan here:*
|
||||
|
||||
- [Change 1 and reason]
|
||||
- [Change 2 and reason]
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
# ============================================================================
|
||||
# PROTOTYPE WORK FILE: [Page Number] [Page Name]
|
||||
# ============================================================================
|
||||
# Purpose: Complete planning document for section-by-section implementation
|
||||
# Created: [Date]
|
||||
# Page Spec: ../[Scenario]/[Page-Number]-[Page-Name]/[Page-Number]-[Page-Name].md
|
||||
# ============================================================================
|
||||
|
||||
metadata:
|
||||
page_number: "[Page-Number]"
|
||||
page_name: "[Page Name]"
|
||||
scenario: "[Scenario-Number]-[Scenario-Name]"
|
||||
complexity: "simple | medium | complex"
|
||||
estimated_sections: [Number]
|
||||
estimated_time: "[X] minutes"
|
||||
|
||||
# Device Compatibility
|
||||
device_compatibility:
|
||||
type: "mobile-only | mobile-tablet | responsive | desktop-only"
|
||||
primary_viewport: "[Width]px"
|
||||
test_viewports:
|
||||
- width: 375
|
||||
height: 667
|
||||
device: "iPhone SE"
|
||||
- width: 393
|
||||
height: 852
|
||||
device: "iPhone 14 Pro"
|
||||
- width: 428
|
||||
height: 926
|
||||
device: "iPhone 14 Pro Max"
|
||||
breakpoints: [] # For mobile-only, leave empty
|
||||
touch_optimized: true
|
||||
hover_interactions: false
|
||||
|
||||
dependencies:
|
||||
- "shared/prototype-api.js"
|
||||
- "shared/init.js"
|
||||
# Add component dependencies as needed
|
||||
|
||||
# ============================================================================
|
||||
# DESIGN TOKENS (Tailwind Config)
|
||||
# ============================================================================
|
||||
|
||||
design_tokens:
|
||||
colors:
|
||||
primary: "#2563eb"
|
||||
primary_hover: "#1d4ed8"
|
||||
success: "#10b981"
|
||||
error: "#ef4444"
|
||||
|
||||
tailwind_config:
|
||||
theme_extend:
|
||||
colors:
|
||||
"[project-name]":
|
||||
50: "#eff6ff"
|
||||
500: "#2563eb"
|
||||
600: "#1d4ed8"
|
||||
700: "#1e40af"
|
||||
fontFamily:
|
||||
sans: "['Inter', 'system-ui', 'sans-serif']"
|
||||
|
||||
components_available:
|
||||
- "image-crop (components/image-crop.js)"
|
||||
- "toast (components/toast.js)"
|
||||
- "modal (components/modal.js)"
|
||||
- "form-validation (components/form-validation.js)"
|
||||
|
||||
# ============================================================================
|
||||
# PAGE REQUIREMENTS (from specification)
|
||||
# ============================================================================
|
||||
|
||||
page_purpose: |
|
||||
[Brief description of what this page does and why user is here]
|
||||
|
||||
user_context:
|
||||
- [Context point 1: What user has done before arriving]
|
||||
- [Context point 2: What data is available]
|
||||
- [Context point 3: User's current state]
|
||||
|
||||
success_criteria:
|
||||
- [Criterion 1: What must be accomplished]
|
||||
- [Criterion 2: Required validations]
|
||||
- [Criterion 3: Data that must be saved]
|
||||
- [Criterion 4: Where user navigates on success]
|
||||
|
||||
# ============================================================================
|
||||
# DEMO DATA REQUIREMENTS
|
||||
# ============================================================================
|
||||
|
||||
demo_data_needed:
|
||||
current_user:
|
||||
firstName: "[Example]"
|
||||
lastName: "[Example]"
|
||||
email: "[example@email.com]"
|
||||
|
||||
# Add other demo data needs (family, dogs, etc.)
|
||||
|
||||
example_submission:
|
||||
# Example of completed form data
|
||||
field1: "[value]"
|
||||
field2: "[value]"
|
||||
|
||||
# ============================================================================
|
||||
# OBJECT ID MAP (all interactive elements)
|
||||
# ============================================================================
|
||||
|
||||
object_ids:
|
||||
header:
|
||||
- "[page]-header-back"
|
||||
- "[page]-header-title"
|
||||
|
||||
form_inputs:
|
||||
- "[page]-input-[field1]"
|
||||
- "[page]-input-[field2]"
|
||||
# Add all form fields
|
||||
|
||||
actions:
|
||||
- "[page]-button-submit"
|
||||
# Add all action buttons
|
||||
|
||||
# ============================================================================
|
||||
# SECTION BREAKDOWN (implementation order)
|
||||
# ============================================================================
|
||||
|
||||
sections:
|
||||
- id: "section-1"
|
||||
name: "Page Structure & Header"
|
||||
scope: "HTML skeleton, header with back button, title, main container"
|
||||
files_affected: ["[Page-Number]-[Page-Name].html"]
|
||||
dependencies: []
|
||||
object_ids:
|
||||
- "[page]-header-back"
|
||||
- "[page]-header-title"
|
||||
tailwind_classes:
|
||||
- "Layout: min-h-screen, bg-gray-50"
|
||||
- "Header: bg-white, border-b, px-4, py-3"
|
||||
- "Button: text-gray-600, hover:text-gray-900"
|
||||
acceptance_criteria:
|
||||
- "Header displays with back button and title"
|
||||
- "Back button navigates to previous page"
|
||||
- "Mobile viewport (375px) looks correct"
|
||||
- "Tailwind styles applied correctly"
|
||||
placeholder_message: "🚧 Building the form... Check back in a few minutes!"
|
||||
|
||||
- id: "section-2"
|
||||
name: "[Section Name]"
|
||||
scope: "[What this section adds]"
|
||||
files_affected: ["[Page-Number]-[Page-Name].html"]
|
||||
dependencies: ["[component files if needed]"]
|
||||
object_ids:
|
||||
- "[object-id-1]"
|
||||
- "[object-id-2]"
|
||||
tailwind_classes:
|
||||
- "[List key Tailwind classes to use]"
|
||||
acceptance_criteria:
|
||||
- "[Test 1]"
|
||||
- "[Test 2]"
|
||||
placeholder_message: "[What's coming next]"
|
||||
|
||||
# Add sections 3-6+ as needed
|
||||
|
||||
# ============================================================================
|
||||
# JAVASCRIPT REQUIREMENTS
|
||||
# ============================================================================
|
||||
|
||||
javascript_functions:
|
||||
initialization:
|
||||
- "initPage() - Page-specific initialization"
|
||||
- "[Other init functions]"
|
||||
|
||||
form_handling:
|
||||
- "handleSubmit(event) - Form submission"
|
||||
- "validateForm() - Validate all fields"
|
||||
- "[Other form functions]"
|
||||
|
||||
ui_interactions:
|
||||
- "[Interaction function 1]"
|
||||
- "[Interaction function 2]"
|
||||
|
||||
api_calls:
|
||||
- "DogWeekAPI.[method]([params])"
|
||||
|
||||
feedback:
|
||||
- "showToast(message, type)"
|
||||
- "setLoadingState(isLoading)"
|
||||
- "[Other feedback functions]"
|
||||
|
||||
# ============================================================================
|
||||
# NAVIGATION
|
||||
# ============================================================================
|
||||
|
||||
navigation:
|
||||
previous_page: "[Previous-Page].html"
|
||||
next_page_success: "[Next-Page].html"
|
||||
next_page_cancel: "[Cancel-Page].html"
|
||||
|
||||
# ============================================================================
|
||||
# TESTING CHECKLIST (after all sections complete)
|
||||
# ============================================================================
|
||||
|
||||
testing_checklist:
|
||||
functionality:
|
||||
- "[ ] All form fields work"
|
||||
- "[ ] Validation shows errors correctly"
|
||||
- "[ ] Submit button works"
|
||||
- "[ ] Loading states display"
|
||||
- "[ ] Success feedback shows"
|
||||
- "[ ] Error handling works"
|
||||
- "[ ] Navigation works (back, next)"
|
||||
- "[ ] Data persists (reload page test)"
|
||||
|
||||
mobile_testing:
|
||||
- "[ ] Viewport is correct width"
|
||||
- "[ ] All tap targets min 44x44px"
|
||||
- "[ ] Text is readable (min 16px)"
|
||||
- "[ ] No horizontal scroll"
|
||||
- "[ ] Touch gestures work (if applicable)"
|
||||
|
||||
code_quality:
|
||||
- "[ ] All Object IDs present"
|
||||
- "[ ] Console logs helpful"
|
||||
- "[ ] No console errors"
|
||||
- "[ ] Tailwind classes properly used"
|
||||
- "[ ] Functions documented"
|
||||
|
||||
accessibility:
|
||||
- "[ ] Keyboard navigation works"
|
||||
- "[ ] Form labels present"
|
||||
- "[ ] Error messages clear"
|
||||
- "[ ] Focus states visible"
|
||||
|
||||
# ============================================================================
|
||||
# MIGRATION NOTES (for production)
|
||||
# ============================================================================
|
||||
|
||||
migration_todos:
|
||||
- "Replace DogWeekAPI.[method]() with Supabase calls"
|
||||
- "[Other production migration tasks]"
|
||||
|
||||
# ============================================================================
|
||||
# KNOWN ISSUES / EDGE CASES
|
||||
# ============================================================================
|
||||
|
||||
edge_cases:
|
||||
- "[Edge case 1 to handle]"
|
||||
- "[Edge case 2 to handle]"
|
||||
|
||||
# ============================================================================
|
||||
# COMPLETION CRITERIA
|
||||
# ============================================================================
|
||||
|
||||
definition_of_done:
|
||||
- "All sections implemented and tested"
|
||||
- "All object IDs present and correct"
|
||||
- "All acceptance criteria met"
|
||||
- "Console logs helpful and clear"
|
||||
- "Mobile viewport works perfectly"
|
||||
- "Demo data loads automatically"
|
||||
- "Form validation complete"
|
||||
- "Success/error feedback working"
|
||||
- "Navigation works"
|
||||
- "No console errors"
|
||||
- "Code is clean"
|
||||
- "Story files document all sections"
|
||||
Reference in New Issue
Block a user