Commit Graph
53 Commits
Author SHA1 Message Date
tiennm99 03dbd0bb63 feat(01-04): wire Items components in App
- Import ItemForm and ItemsList
- Add Items section after People section
- App now supports full bill entry workflow
2026-03-12 02:51:44 +00:00
tiennm99 77d0afed54 feat(01-04): create ItemsList component for displaying items
- Subscribes to store.items signal
- Displays each item with name and formatted price
- Includes ItemAssign component for each item
- Shows empty state when no items added
2026-03-12 02:51:29 +00:00
tiennm99 24f5fc14b2 feat(01-04): create ItemAssign component for assigning people
- Multi-select checkbox UI for each person
- Reflects current assignment state
- Toggles person on/off when clicked
- Calls store.setAssignment on change
- Shows hint when no people added
2026-03-12 02:50:57 +00:00
tiennm99 f3214341f0 feat(01-04): create ItemForm component for adding items
- Controlled form with name and price inputs
- Calls store.addItem on form submit
- Shows error message on validation failure
- Clears inputs on successful add
2026-03-12 02:50:31 +00:00
tiennm99andClaude Opus 4.6 708ec8d520 feat(01-03): wire components in App with Vite + Preact setup
- Create App.jsx importing PersonForm and PeopleList
- Create main.jsx entry point for Preact render
- Add index.html as app entry point
- Add basic CSS styles for layout
- Configure Vite with @preact/preset-vite
- Update vitest config for Preact/jsdom environment
- Add missing dependencies (preact, vite, @preact/preset-vite)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 02:46:25 +00:00
tiennm99andClaude Opus 4.6 a483f2e476 feat(01-03): create PeopleList component
- Subscribes to store.people signal
- Renders list of person names with unique keys
- Shows empty state when no people added
- Preact Signals auto-track dependencies in components

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 02:46:12 +00:00
tiennm99andClaude Opus 4.6 aeb613c36a feat(01-03): create PersonForm component
- Controlled form with useState for local state
- Calls store.addPerson on form submit
- Shows error message if addPerson fails
- Clears input on successful add
- Uses Preact hooks for form state management

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 02:46:06 +00:00
tiennm99andClaude Opus 4.6 51564970e2 fix(01-01): fix syntax error in billStore.js negation operator
- Fix escaped exclamation marks (\! to !) in condition checks
- Add currency utility module (dollarsToCents, centsToDollars, formatCurrency)

All 20 tests now pass (10 billStore + 10 currency)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 02:42:28 +00:00
tiennm99 12e66bd2da chore: reset 2026-03-11 22:35:40 +07:00
tiennm99 1fad9a89a9 Archive v1.0 planning; finalize MVP docs
Archive and finalize v1.0 milestone and planning artifacts: add MILESTONES.md, milestone archives (v1.0-ROADMAP.md, v1.0-REQUIREMENTS.md), verification and phase artifacts, and update phase/roadmap/project state to mark v1.0 MVP as shipped. Tidy PROJECT and ROADMAP to reflect shipped features and next steps, remove the old REQUIREMENTS.md (moved to milestone archive), and update STATE.md to show milestone completion. Minor code change: expose GridManager.events as public readonly to allow external subscribers. Package metadata: normalize keywords formatting and add an optional dependency entry in package.json for rollup native binary support.
2026-03-11 21:54:55 +07:00
tiennm99andClaude 41961894c6 fix(06): connect GridManager events to Game match handler
Bug: Game was listening on its own events emitter for 'tilesSelected'
but GridManager emits on its own events emitter.
Fix: Changed listener from this.events to this.gridManager.events


🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 14:18:03 +00:00
tiennm99andClaude 085556c274 feat(06-01): integrate MatchAnimation into Renderer with animateMatch method
- Add animateMatch() method to start animations for matched tiles
- Apply scale/alpha transforms in renderTile() when animation exists
- Clean up completed animations from matchAnimations map
- Add tests for animateMatch integration


🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 13:49:18 +00:00
tiennm99andClaude d0b718a487 feat(06-02): wire animateMatch call to tilesMatched event
- Add animation.matchDuration=250 to CONFIG
- Add matchAnimations Map and animateMatch method to Renderer
- Apply scale+alpha transforms in renderTile for animating tiles
- Wire animateMatch call in Game.ts after drawPath

This completes the dependency from plan 06-01 (Rule 2 auto-fix for missing critical functionality)


🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 13:49:11 +00:00
tiennm99andClaude f6e0e6cc37 feat(06-01): add animation.matchDuration to CONFIG
- Add animation configuration object with matchDuration: 250ms
- Per CONTEXT.md recommendation (200-300ms range)


🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 13:46:56 +00:00
tiennm99andClaude 4dfa4e2e02 feat(06-02): add glow effect to path line visualization
- Add shadowBlur=15 and shadowColor='#00ff00' for green glow
- Wrap path drawing in ctx.save()/ctx.restore() for state preservation
- Add tests for glow effect properties (shadowBlur, shadowColor, save/restore)


🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 13:44:57 +00:00
tiennm99andClaude 788892458c feat(06-03): trigger ripple effect on touch/click input
- Add renderer.addRipple(x, y) call in handleInput method
- Ripple appears at touch/click coordinates before tile selection
- Provides visual feedback for all input interactions


🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 13:41:57 +00:00
tiennm99andClaude 059012f9ac feat(06-03): add RippleAnimation class for touch feedback
- Add RippleAnimation class with 300ms duration and 40px max radius
- Add rippleAnimations property to Renderer
- Add addRipple(x, y) method to trigger ripple effect
- Render ripples in main render loop with auto-cleanup
- Use selection color (rgba 233, 69, 96) for visual consistency


🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 13:40:52 +00:00
tiennm99 ff3f3a7b9b Add game-state planning, NoMovesDetector & overlay
Update project planning and implement game-end components: tweak .gitignore entries, bump planning state progress, and add extensive Phase 04 artifacts (plans, summary, research, validation) plus Phase 05 initial plan. Deliverables include index.html overlay for game-over UI and modifications to src/game/Game.ts and src/models/Tile.ts to support win / no-moves detection and restart hooks; also add GameStateManager / NoMovesDetector integration notes and tests scaffolding in planning docs. Purpose: prepare and record implementation details for win/lose detection, game-over overlay, input blocking, reset/restart flow, and board-generation recovery.
2026-03-11 17:45:14 +07:00
tiennm99andClaude fa397628c1 feat(05-01): implement random board generation with Fisher-Yates shuffle
- Add board:generated event to GameEvents interface
 - implement generateRandomGrid() method with solvability verification
 - Enhance initializeGrid() to use NoMovesDetector for verification
 - Add tests for random board generation
 - add import for NoMovesDetector

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 10:09:23 +00:00
tiennm99andClaude 18ae20eaeb feat(05-02): implement shuffleTiles() method with Fisher-Yates shuffle
- shuffleTiles() redistributes tile types while preserving positions
- Clears selection to prevent stale tile references
- Emits board:shuffling before and board:shuffled after
- Handles partially cleared boards correctly
- Added comprehensive test coverage for shuffle functionality


🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 10:05:43 +00:00
tiennm99andClaude a73f3edf1d test(05-02): add shuffle events to GameEvents interface
- Add board:shuffling event with tilesRemaining payload
- Add board:shuffled event with tilesRemaining payload
- These events enable UI feedback during tile shuffle recovery


🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 10:02:14 +00:00
tiennm99andClaude cc3aaced1b feat(04-04): wire up restart button click handler
- Added restart button click event listener in Game constructor
- Click handler calls restart() method to reset game
- Null check for restart button element (defensive programming)
- Tests verify click handler registration and restart() invocation
- Tests verify graceful handling of missing button element


🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 08:29:09 +00:00
tiennm99andClaude 7a4cea2fbc feat(04-04): implement restart() method with score preservation
- Added previousScore property to Game class
- Implemented restart() method that stores previousScore, resets grid/score/state/UI
- Added updatePreviousScoreDisplay() helper method
- Added game:restart event type to GameEvents interface
- Restart resets grid via initializeGrid(), resets score to 0, transitions state to IDLE
- Previous score preserved and displayed, hidden if 0
- All tests passing for restart functionality


🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 08:28:44 +00:00
tiennm99andClaude f63bb9c8ed test(04-04): add previous score display tests and HTML element
- Added tests for previous-score-display element existence and styling
- Added previous-score-display div to HTML with score-container wrapper
- Element initially hidden with display: none
- Follows existing score-display pattern for consistency


🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 08:28:07 +00:00
tiennm99andClaude 35c085d445 feat(04-02): add game over HTML overlay
- Added game-over-overlay div with fixed positioning and z-index 1000
- Added overlay-content div with semi-transparent background
- Added game-over-message h1 element for win/lose message
- Added restart-button button with hover effect
- Overlay hidden by default (display: none)
- Styling matches score overlay pattern

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 08:21:27 +00:00
tiennm99andClaude 5bee444a5c test(04-02): add failing test for NoMovesDetector
- Created comprehensive test suite with 10 test cases
- Tests cover valid/invalid move detection, type-optimization, edge cases
- Tests verify path detection with 0, 1, and 2 turns
- Tests verify cleared tiles are skipped and empty board handling

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 08:21:03 +00:00
tiennm99andClaude d70271878f feat(04-01): implement GameStateManager class with transition validation
- Add transition validation with validTransitions mapping
- Implement transitionTo() method with validation and event emission
- Add canSelectTile() helper method for input blocking
- Add reset() method for restart functionality
- Update tests to cover all state machine behavior (7 test cases)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 08:16:08 +00:00
tiennm99andClaude 83cba85f04 feat(04-01): add GameState enum and StateChangeEvent type
- Add GameState enum with 4 string values (IDLE, SELECTING, MATCHING, GAME_OVER)
- Add StateChangeEvent interface with from/to properties
- Add game:stateChange event to GameEvents interface
- Add tests for GameState enum values and types

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 08:14:41 +00:00
tiennm99andClaude Opus 4.6 b00b438b1b docs(phase-3): add planning artifacts and update state for Phase 4
Add Phase 3 plan files (pathfinding, scoring, visual feedback),
update state tracking for Phase 4 context, and clean up gitignore.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 14:04:38 +07:00
tiennm99andClaude Opus 4.6 a94c0c4ac3 docs(phase-3): complete Core Matching Mechanics phase and prepare Phase 4
Mark Phase 3 as completed in roadmap, add verification documentation,
and create Phase 4 context for Game State Management.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 14:03:31 +07:00
tiennm99andClaude 0c74c254c2 feat(03-02): extend Game.ts with match handling and score display
- Added MatchEngine instantiation in Game constructor
- Implemented tilesSelected event handler with match validation
- Valid matches: emit tilesMatched event, clear tiles, update score display
- Failed matches: emit matchFailed event, deselect after 200ms delay
- Added private score property tracking current score
- Added updateScoreDisplay method to update HTML overlay
- Extended GameEvents interface with tilesMatched and matchFailed events

Event flow:
1. Player selects two tiles → tilesSelected event
2. MatchEngine validates match (type → position → path)
3. Success: clear tiles, add score, update display, emit tilesMatched
4. Failure: emit matchFailed, deselect tiles after 200ms


🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 04:41:30 +00:00
tiennm99andClaude 10de5e4b6f feat(03-02): add clearTiles method to GridManager
- Added clearTiles method that marks tiles as cleared
- Emits tile:cleared event for each cleared tile
- Calls deselectAll() after clearing tiles
- Enables CORE-05 (connected tiles disappear) and CORE-07 (cleared tiles become passable)


🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 04:40:30 +00:00
tiennm99andClaude fd3b584fff feat(03-02): implement MatchEngine validation pipeline
- Implemented MatchEngine class with validateMatch method
- Multi-stage validation: type check → position check → pathfinding
- Type check happens before expensive pathfinding (fail-fast optimization)
- Returns MatchResult with valid flag, reason, path, turns, and score
- All 8 test cases covering all validation branches
- Score calculated using Scoring.calculate

Test coverage:
- Different tile types → valid=false, reason='different-type'
- Same tile ID → valid=false, reason='same-tile'
- Valid 0-turn path → valid=true, turns=0, score=150
- Valid 1-turn path → valid=true, turns=1, score=125
- Valid 2-turn path → valid=true, turns=2, score=100
- 3+ turn path → valid=false, reason='too-many-turns'
- No path (blocked) → valid=false, reason='no-path'
- Score calculation correct for valid matches


🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 04:40:00 +00:00
tiennm99andClaude 6829c2f025 test(03-02): add failing test for Scoring system
- Created test suite with 5 test cases for score calculation
- Tests cover base score (100), 0-turn bonus (150), 1-turn bonus (125)
- Tests verify default to base score for invalid turn counts
- Tests verify integer scores (no floating point)


🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 04:39:00 +00:00
tiennm99andClaude 472f1529b9 feat(03-01): implement BFS pathfinding with turn counting
- Created PathFinder class with static findPath method
- Implements BFS algorithm with turn tracking (max 2 turns default)
- Direction encoding: -1=none/start, 0=up, 1=right, 2=down, 3=left
- Visited state tracking using 'row,col,direction' keys to avoid cycles
- Turn counting: only direction changes count, first move doesn't count
- Boundary checking using CONFIG.grid.rows and CONFIG.grid.cols
- Passable tile check: only cleared tiles are traversable
- Returns PathNode with path array and turn count, or null if no path
- Added 10 comprehensive test cases covering:
  * 0-turn paths (direct horizontal/vertical)
  * 1-turn L-shaped paths
  * 2-turn Z-shaped paths
  * 3+ turn rejection
  * Blocked path detection
  * Path inclusion of start/end positions
  * Correct turn counting behavior

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 04:36:00 +00:00
tiennm99andClaude 9fc8962dc7 test(03-01): add PathNode and MatchResult type definitions
- Added PathNode interface for BFS state tracking (row, col, direction, turns, path)
- Added MatchResult interface for match validation results (valid, reason, path, turns)
- Created test file with type import tests
- Direction encoding: -1=none/start, 0=up, 1=right, 2=down, 3=left

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 04:35:00 +00:00
tiennm99andClaude acee40b83b chore: update .gitignore and add Phase 2 artifacts
- Add shell configs, IDE files, and local configs to .gitignore
- Add Phase 2 planning artifacts (CONTEXT, RESEARCH, VERIFICATION)
- Add GridManager test suite


🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 03:09:45 +00:00
tiennm99andClaude 80a9e2b95c feat(02-03): add debounced canvas resize handler
- Add resizeTimeout field for debounce timer
- Add handleResize() method with 150ms debounce
- Add window resize event listener
- Recalculate canvas size on resize
- Trigger renderer.render() after resize

Implements Task 3 of plan 02-03


🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 02:56:33 +00:00
tiennm99andClaude 86f968e5ad feat(02-03): add mouse and touch event listeners with coordinate-to-tile mapping
- Add setupInputListeners() public method
- Add handleClick and handleTouch event handlers
- Add handleInput() method for coordinate translation
- Implement getBoundingClientRect() for accurate hit detection
- Account for device pixel ratio in coordinate calculations
- Map canvas coordinates to tile grid position
- Call gridManager.selectTile() for valid tiles

Implements Task 2 of plan 02-03


🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 02:56:11 +00:00
tiennm99andClaude dedc65e2c7 feat(02-03): integrate GridManager and Renderer into Game class
- Add GridManager and Renderer imports
- Add gridManager and renderer fields to Game class
- Initialize GridManager and create grid in constructor
- Initialize Renderer with canvas reference
- Delegate render() to renderer.render()
- Add tilesSelected event listener for logging

Implements Task 1 of plan 02-03


🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 02:55:44 +00:00
tiennm99andClaude 4f1a0fdb9d feat(02-02): implement Renderer class with tile and selection rendering
- Created Renderer class with tile rendering, selection highlighting, and fade-in animations
- Created GridManager class with tile array and selection state management (blocking dependency)
- Added tilesSelected event to GameEvents interface
- Created comprehensive test suite for Renderer with 9 test cases

**Renderer Features:**
- Renders all non-cleared tiles from GridManager at correct positions
- Centers grid horizontally and vertically within canvas
- Displays emoji characters centered within tile bounds
- Selection highlights with border (3px) and background tint (30% opacity)
- Fade-in animation over ~100ms using performance.now()
- Uses CONFIG.tile.size, gap, cornerRadius for positioning
- Respects CONFIG.colors for styling

**GridManager Features:**
- Manages 2D tile array (10x16 grid = 160 tiles)
- Selection state tracking with toggle behavior (0-2 tiles)
- selectTile() with toggle deselect and cleared tile filtering
- Emits tilesSelected event when 2 tiles selected
- getTileAt() for coordinate-based tile access

**Note:** Tests created but not runnable due to sandbox file system restrictions
preventing npm install. Implementation verified manually against plan requirements.


🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-11 02:51:11 +00:00
tiennm99andClaude Opus 4.6 4adca72e3a fix(01-02): replace Node EventEmitter with browser-compatible implementation
Node's 'events' module doesn't work in browsers. Replaced with custom
Map-based implementation that provides the same API but works in both
Node.js and browser environments.

All 98 tests still pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 00:09:45 +07:00
tiennm99andClaude Opus 4.6 d0a39fc031 feat(01-03): wire main.ts entry point with Game class
- Update main.ts to import and instantiate Game class
- Wait for DOMContentLoaded before initializing
- Add error event listener on game.events
- Add tick event listener (commented debug logging)
- Log initialization message to console
- Add main.test.ts with entry point tests (4 tests)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 00:00:32 +07:00
tiennm99andClaude Opus 4.6 6fbe23365a feat(01-03): add Game class orchestrator with TDD
- Create Game class that orchestrates canvas, game loop, and events
- Game constructor initializes canvas, context, loop, and events
- Game.start() starts the loop and emits 'game:start' event
- Game.render() clears canvas with background color
- Handle device pixel ratio for sharp rendering
- Add comprehensive tests (15 tests) for Game class

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 23:58:18 +07:00
tiennm99andClaude Opus 4.6 fdd1ffd17e refactor(01-02): fix TypeScript type issues in tests
- Changed EventEmitter generic constraint to `object` for interface compatibility
- Fixed GameLoop test closure variable narrowing issue by using array

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 23:51:35 +07:00
tiennm99andClaude Opus 4.6 efcac7ad4c feat(01-02): implement Tile model class
- Implements Tile interface from types/index.ts
- Constructor with id, type, position
- emoji getter returns CONFIG.emojis[type]
- isAdjacent() checks orthogonal adjacency
- cleared property defaults to false

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 23:49:16 +07:00
tiennm99andClaude Opus 4.6 080b9f22a0 test(01-02): add failing tests for Tile model class
- Tests for constructor with id, type, position
- Tests for emoji getter returning correct emoji
- Tests for isAdjacent() method with various positions
- Tests for readonly properties and cleared default

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 23:48:48 +07:00
tiennm99andClaude Opus 4.6 84e92c3178 feat(01-02): implement TypedEventEmitter class
- Generic class wrapping Node's EventEmitter
- Type-safe on(), emit(), off() methods
- Added once() and removeAllListeners() methods
- Returns this for method chaining

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 23:47:55 +07:00
tiennm99andClaude Opus 4.6 4609e01410 test(01-02): add failing tests for TypedEventEmitter class
- Tests for on(), emit(), off() methods
- Tests for multiple listeners and chaining
- Tests for type safety with typed payloads
- Tests for error handling and once() method
- Tests for removeAllListeners()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 23:47:24 +07:00
tiennm99andClaude Opus 4.6 e46bc1db7a test(01-02): add failing tests for GameLoop class
- Tests for start(), stop(), isRunning(), getRafId()
- Tests for update callback with deltaTime
- Tests for delta time accumulation
- Tests for loop lifecycle (start/stop multiple times)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 23:46:34 +07:00