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:
2025-10-04 19:20:25 +08:00
parent 17730359b8
commit 325eeaf063
6 changed files with 2214 additions and 0 deletions

View 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)