feat(spec): complete implementation plan for Halloween event page
Created comprehensive planning documentation following Spec-Kit standards: Phase 0 - Research: - Analyzed 10 technical decisions (HTML5, ASCII art, countdown, responsive design) - Resolved edge cases (post-countdown, post-event, JS disabled, small screens) - Documented rationale and alternatives for each decision Phase 1 - Design: - Defined 4 data entities (Event, Countdown, VisualTheme, Content) - Created JSON schema contract for page content validation - Mapped 35 functional requirements to implementation approach - Generated quickstart testing checklist with 10 test categories Technical decisions: - Single-file HTML architecture (no external dependencies) - Pure CSS animations (blinking cursor) - JavaScript countdown with GMT+8 timezone handling - Responsive ASCII art (desktop/mobile versions) - WCAG 2.1 AA compliance (21:1 contrast ratio) Constitution compliance: PASS - No violations detected - Follows user-centric, professional excellence principles - Minimal technical stack aligns with simplicity requirement Artifacts created: - spec.md: 35 functional requirements - plan.md: Implementation strategy and phases - research.md: 10 technical research decisions - data-model.md: 4 entity definitions with validation - contracts/page-content.schema.json: JSON Schema - quickstart.md: Comprehensive testing checklist Ready for: /tasks command to generate tasks.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
364
specs/001-mcdonald-s-it/data-model.md
Normal file
364
specs/001-mcdonald-s-it/data-model.md
Normal file
@@ -0,0 +1,364 @@
|
||||
# Data Model: McDonald's IT Halloween Event Page
|
||||
|
||||
**Feature**: Halloween event announcement webpage
|
||||
**Date**: 2025-10-04
|
||||
**Status**: Complete
|
||||
|
||||
## Overview
|
||||
|
||||
This document defines the data entities and their relationships for the Halloween event page. While this is a static page with no backend storage, defining the data model helps structure the HTML content and JavaScript logic.
|
||||
|
||||
## Entities
|
||||
|
||||
### 1. Event
|
||||
|
||||
Represents the Halloween event information.
|
||||
|
||||
**Purpose**: Store and display all event details
|
||||
|
||||
**Fields**:
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `title` | String | Yes | - | Event title: "2025 MITA Halloween" |
|
||||
| `date` | Date | Yes | - | Event date: October 31, 2025 |
|
||||
| `startTime` | Time | Yes | - | Event start: 18:00 GMT+8 |
|
||||
| `endTime` | Time | Yes | - | Event end: 21:00 GMT+8 |
|
||||
| `timezone` | String | Yes | "GMT+8" | Timezone for event |
|
||||
| `location` | String | Yes | - | "MITA Building 2F Pantry" |
|
||||
| `organizer` | Object | Yes | - | Organizer details |
|
||||
| `organizer.name` | String | Yes | - | "Jessi Pan" |
|
||||
| `organizer.email` | String | Yes | - | "jessi.pan@cn.mcd.com" |
|
||||
| `activities` | Array<String> | Yes | - | List of activities |
|
||||
| `notes` | Array<String> | Yes | - | Additional information |
|
||||
|
||||
**Validation Rules**:
|
||||
- `date` must be valid ISO date format
|
||||
- `startTime` must be before `endTime`
|
||||
- Times must be in 24-hour format (HH:MM)
|
||||
- `organizer.email` must be valid email format
|
||||
- `activities` must contain at least 1 item
|
||||
|
||||
**Example**:
|
||||
```json
|
||||
{
|
||||
"title": "2025 MITA Halloween",
|
||||
"date": "2025-10-31",
|
||||
"startTime": "18:00",
|
||||
"endTime": "21:00",
|
||||
"timezone": "GMT+8",
|
||||
"location": "MITA Building 2F Pantry",
|
||||
"organizer": {
|
||||
"name": "Jessi Pan",
|
||||
"email": "jessi.pan@cn.mcd.com"
|
||||
},
|
||||
"activities": [
|
||||
"Cosplay (encouraged)",
|
||||
"Bug Debugging Games",
|
||||
"Lucky Draw"
|
||||
],
|
||||
"notes": [
|
||||
"Costumes encouraged",
|
||||
"Food and drinks provided"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**State Transitions**: None (static data)
|
||||
|
||||
---
|
||||
|
||||
### 2. Countdown
|
||||
|
||||
Represents the countdown timer state and logic.
|
||||
|
||||
**Purpose**: Calculate and display time remaining until event
|
||||
|
||||
**Fields**:
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `targetDateTime` | DateTime | Yes | - | Oct 31 2025 18:00:00 GMT+8 (in UTC: 10:00:00 UTC) |
|
||||
| `currentDateTime` | DateTime | Yes | - | User's current time (updated every second) |
|
||||
| `timeRemaining` | Object | No | null | Calculated time remaining |
|
||||
| `timeRemaining.days` | Integer | No | 0 | Days remaining |
|
||||
| `timeRemaining.hours` | Integer | No | 0 | Hours remaining (0-23) |
|
||||
| `timeRemaining.minutes` | Integer | No | 0 | Minutes remaining (0-59) |
|
||||
| `timeRemaining.seconds` | Integer | No | 0 | Seconds remaining (0-59) |
|
||||
| `status` | Enum | Yes | "before" | Current event status |
|
||||
|
||||
**Status Values**:
|
||||
- `before`: Current time is before event start (show countdown)
|
||||
- `active`: Event has started, current time is between start and end (show "EVENT STARTING NOW!")
|
||||
- `ended`: Event has ended, current time is after event end (show "EVENT COMPLETED")
|
||||
|
||||
**Validation Rules**:
|
||||
- `targetDateTime` must be a valid Date object
|
||||
- All time values must be >= 0
|
||||
- `status` must be one of: "before", "active", "ended"
|
||||
- `timeRemaining` is null when status is not "before"
|
||||
|
||||
**Calculation Logic**:
|
||||
```javascript
|
||||
// Convert GMT+8 to UTC for calculation
|
||||
// Oct 31 2025 18:00 GMT+8 = Oct 31 2025 10:00 UTC
|
||||
const targetDate = new Date('2025-10-31T10:00:00Z');
|
||||
const eventEnd = new Date('2025-10-31T13:00:00Z'); // 21:00 GMT+8
|
||||
|
||||
function updateCountdown() {
|
||||
const now = new Date();
|
||||
|
||||
if (now >= eventEnd) {
|
||||
return { status: 'ended', timeRemaining: null };
|
||||
}
|
||||
|
||||
if (now >= targetDate) {
|
||||
return { status: 'active', timeRemaining: null };
|
||||
}
|
||||
|
||||
const diff = targetDate - now;
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
||||
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
|
||||
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
|
||||
|
||||
return {
|
||||
status: 'before',
|
||||
timeRemaining: { days, hours, minutes, seconds }
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
**State Transitions**:
|
||||
```
|
||||
before → active (when current time >= targetDateTime)
|
||||
active → ended (when current time >= eventEnd)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. VisualTheme
|
||||
|
||||
Represents the visual design configuration.
|
||||
|
||||
**Purpose**: Define and apply consistent styling across the page
|
||||
|
||||
**Fields**:
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `backgroundColor` | Color | Yes | "#000000" | Pure black background |
|
||||
| `primaryColor` | Color | Yes | "#00FF00" | Terminal green for main text |
|
||||
| `accentColor` | Color | Yes | "#FFBF00" | Amber for key information |
|
||||
| `fontFamily` | String | Yes | See below | Monospace font stack |
|
||||
| `fontSize` | Object | Yes | - | Font sizes for different elements |
|
||||
| `fontSize.base` | String | Yes | "16px" | Base font size |
|
||||
| `fontSize.heading` | String | Yes | "20px" | Heading size |
|
||||
| `fontSize.small` | String | Yes | "14px" | Small text size |
|
||||
| `asciiArt` | Object | Yes | - | ASCII art content |
|
||||
| `asciiArt.logoDesktop` | String | Yes | - | McDonald's logo for desktop |
|
||||
| `asciiArt.logoMobile` | String | Yes | - | McDonald's logo for mobile |
|
||||
| `asciiArt.decorations` | Array<String> | Yes | - | Halloween ASCII decorations |
|
||||
|
||||
**Font Stack**:
|
||||
```css
|
||||
"Courier New", Monaco, Consolas, "Liberation Mono", Menlo, monospace
|
||||
```
|
||||
|
||||
**Validation Rules**:
|
||||
- All color values must be valid hex format (#RRGGBB)
|
||||
- `backgroundColor` must be #000000 (black)
|
||||
- `primaryColor` and `accentColor` must meet WCAG AA contrast ratio with background
|
||||
- `fontFamily` must include at least one monospace fallback
|
||||
- ASCII art strings must contain only ASCII characters (0-127)
|
||||
|
||||
**Example**:
|
||||
```json
|
||||
{
|
||||
"backgroundColor": "#000000",
|
||||
"primaryColor": "#00FF00",
|
||||
"accentColor": "#FFBF00",
|
||||
"fontFamily": "\"Courier New\", Monaco, Consolas, Menlo, monospace",
|
||||
"fontSize": {
|
||||
"base": "16px",
|
||||
"heading": "20px",
|
||||
"small": "14px"
|
||||
},
|
||||
"asciiArt": {
|
||||
"logoDesktop": "[Multi-line ASCII art]",
|
||||
"logoMobile": "[Simplified ASCII art]",
|
||||
"decorations": ["🎃", "👻", "🦇"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**State Transitions**: None (static configuration)
|
||||
|
||||
---
|
||||
|
||||
### 4. Content (Bilingual)
|
||||
|
||||
Represents bilingual text content structure.
|
||||
|
||||
**Purpose**: Store and display English/Chinese content pairs
|
||||
|
||||
**Fields**:
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `section` | String | Yes | - | Content section identifier |
|
||||
| `english` | String | Yes | - | English text |
|
||||
| `chinese` | String | Yes | - | Chinese text (Simplified) |
|
||||
| `order` | Integer | Yes | - | Display order |
|
||||
|
||||
**Example Content Pairs**:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"section": "date",
|
||||
"english": "Date: October 31, 2025",
|
||||
"chinese": "日期:2025年10月31日",
|
||||
"order": 1
|
||||
},
|
||||
{
|
||||
"section": "time",
|
||||
"english": "Time: 18:00 - 21:00 (6:00 PM - 9:00 PM)",
|
||||
"chinese": "时间:18:00 - 21:00",
|
||||
"order": 2
|
||||
},
|
||||
{
|
||||
"section": "location",
|
||||
"english": "Location: MITA Building 2F Pantry",
|
||||
"chinese": "地点:MITA大厦2楼茶水间",
|
||||
"order": 3
|
||||
},
|
||||
{
|
||||
"section": "organizer",
|
||||
"english": "Organizer: Jessi Pan (jessi.pan@cn.mcd.com)",
|
||||
"chinese": "组织者:Jessi Pan (jessi.pan@cn.mcd.com)",
|
||||
"order": 4
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Display Pattern**:
|
||||
```html
|
||||
<p lang="en">{english}</p>
|
||||
<p lang="zh-CN">{chinese}</p>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Relationships
|
||||
|
||||
```
|
||||
Event (1)
|
||||
├── has timezone (1) → Countdown
|
||||
└── has theme (1) → VisualTheme
|
||||
|
||||
Countdown (1)
|
||||
└── uses targetDateTime from Event
|
||||
|
||||
VisualTheme (1)
|
||||
└── styles all Content
|
||||
|
||||
Content (many)
|
||||
└── derived from Event
|
||||
```
|
||||
|
||||
## HTML Representation
|
||||
|
||||
The data model maps to HTML structure as follows:
|
||||
|
||||
```html
|
||||
<main>
|
||||
<!-- VisualTheme.asciiArt.logoDesktop -->
|
||||
<header>
|
||||
<pre class="ascii-desktop">[McDonald's ASCII Logo]</pre>
|
||||
<pre class="ascii-mobile">[McDonald's ASCII Logo Simplified]</pre>
|
||||
<p class="tagline">i'm lovin' IT<span class="cursor">_</span></p>
|
||||
</header>
|
||||
|
||||
<!-- Countdown -->
|
||||
<section class="countdown">
|
||||
<div id="countdown-display">
|
||||
<span id="days">0</span>d
|
||||
<span id="hours">0</span>h
|
||||
<span id="minutes">0</span>m
|
||||
<span id="seconds">0</span>s
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Event + Content (bilingual) -->
|
||||
<section class="event-info">
|
||||
<p lang="en">Date: {Event.date}</p>
|
||||
<p lang="zh-CN">日期:{Event.date}</p>
|
||||
<!-- ... more bilingual pairs -->
|
||||
</section>
|
||||
|
||||
<!-- Event.activities -->
|
||||
<section class="activities">
|
||||
<p lang="en">Activities:</p>
|
||||
<p lang="zh-CN">活动:</p>
|
||||
<ul>
|
||||
<li lang="en">{activity}</li>
|
||||
<li lang="zh-CN">{activity_zh}</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- VisualTheme.asciiArt.decorations -->
|
||||
<section class="decorations">
|
||||
<pre>{decoration}</pre>
|
||||
</section>
|
||||
</main>
|
||||
```
|
||||
|
||||
## JavaScript Data Structure
|
||||
|
||||
```javascript
|
||||
const eventData = {
|
||||
event: {
|
||||
title: "2025 MITA Halloween",
|
||||
date: "2025-10-31",
|
||||
startTime: "18:00",
|
||||
endTime: "21:00",
|
||||
timezone: "GMT+8",
|
||||
location: "MITA Building 2F Pantry",
|
||||
organizer: {
|
||||
name: "Jessi Pan",
|
||||
email: "jessi.pan@cn.mcd.com"
|
||||
},
|
||||
activities: ["Cosplay", "Bug Debugging Games", "Lucky Draw"],
|
||||
notes: ["Costumes encouraged", "Food and drinks provided"]
|
||||
},
|
||||
|
||||
countdown: {
|
||||
targetDateTime: new Date('2025-10-31T10:00:00Z'), // GMT+8 = UTC+8
|
||||
eventEnd: new Date('2025-10-31T13:00:00Z'),
|
||||
status: 'before',
|
||||
timeRemaining: null
|
||||
},
|
||||
|
||||
theme: {
|
||||
backgroundColor: "#000000",
|
||||
primaryColor: "#00FF00",
|
||||
accentColor: "#FFBF00",
|
||||
fontFamily: '"Courier New", Monaco, Consolas, Menlo, monospace'
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Validation Contract
|
||||
|
||||
All data must conform to the schema defined in `/contracts/page-content.schema.json`.
|
||||
|
||||
See: [page-content.schema.json](./contracts/page-content.schema.json)
|
||||
|
||||
## Notes
|
||||
|
||||
- This is a static page, so data is hardcoded in HTML/JS
|
||||
- No database or API calls required
|
||||
- Data updates only via code changes
|
||||
- Countdown is the only dynamic data (calculated client-side)
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Create contracts/page-content.schema.json (JSON Schema validation)
|
||||
- Create quickstart.md (testing checklist)
|
||||
- Update CLAUDE.md (agent context)
|
||||
Reference in New Issue
Block a user