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>
365 lines
10 KiB
Markdown
365 lines
10 KiB
Markdown
# 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)
|