<!DOCTYPE html>
<html ng-app="heroesApp">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tour of Heroes</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 40px;
animation: fadeInDown 0.8s ease;
}
.header h1 {
font-size: 3rem;
font-weight: 700;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
}
.header p {
font-size: 1.2rem;
opacity: 0.95;
}
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.nav {
background: white;
border-radius: 12px;
padding: 15px 20px;
margin-bottom: 30px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.nav-btn {
padding: 12px 24px;
background: #f5f5f5;
border: 2px solid transparent;
border-radius: 8px;
cursor: pointer;
font-size: 15px;
font-weight: 600;
transition: all 0.3s;
color: #555;
}
.nav-btn:hover {
background: #e8f5e9;
color: #27ae60;
border-color: #27ae60;
}
.nav-btn.active {
background: linear-gradient(135deg, #27ae60 0%, #229954 100%);
color: white;
box-shadow: 0 4px 12px rgba(39, 174, 96, 0.3);
}
.card {
background: white;
border-radius: 16px;
padding: 30px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
animation: fadeInUp 0.6s ease;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card-title {
font-size: 2rem;
color: #2c3e50;
margin-bottom: 25px;
font-weight: 700;
}
/* Dashboard Styles */
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
margin-top: 20px;
}
.hero-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
text-align: center;
}
.hero-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
}
.hero-card .hero-name {
font-size: 1.5rem;
font-weight: 700;
margin-bottom: 10px;
}
.hero-card .hero-power {
font-size: 1rem;
opacity: 0.9;
}
/* Heroes List Styles */
.search-box {
margin-bottom: 25px;
position: relative;
}
.search-box input {
width: 100%;
padding: 15px 20px;
border: 2px solid #e0e0e0;
border-radius: 10px;
font-size: 16px;
transition: all 0.3s;
}
.search-box input:focus {
outline: none;
border-color: #27ae60;
box-shadow: 0 0 0 3px rgba(39, 174, 96, 0.1);
}
.add-hero-btn {
background: linear-gradient(135deg, #27ae60 0%, #229954 100%);
color: white;
border: none;
padding: 12px 24px;
border-radius: 10px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
margin-bottom: 20px;
box-shadow: 0 4px 12px rgba(39, 174, 96, 0.3);
}
.add-hero-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(39, 174, 96, 0.4);
}
.heroes-list {
display: grid;
gap: 12px;
}
.hero-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 18px 20px;
background: #f8f9fa;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s;
border: 2px solid transparent;
}
.hero-item:hover {
background: #e8f5e9;
border-color: #27ae60;
transform: translateX(5px);
}
.hero-info {
display: flex;
align-items: center;
gap: 15px;
flex: 1;
}
.hero-badge {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 18px;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
.hero-details h3 {
font-size: 18px;
color: #2c3e50;
margin-bottom: 4px;
}
.hero-details p {
font-size: 14px;
color: #7f8c8d;
}
.hero-actions {
display: flex;
gap: 8px;
}
.action-btn {
background: white;
border: 2px solid #e0e0e0;
padding: 8px 16px;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
transition: all 0.3s;
}
.action-btn.edit:hover {
background: #fff3cd;
border-color: #ffc107;
color: #856404;
}
.action-btn.delete:hover {
background: #f8d7da;
border-color: #dc3545;
color: #721c24;
}
/* Hero Detail Styles */
.hero-detail {
max-width: 600px;
margin: 0 auto;
}
.hero-detail-header {
text-align: center;
padding: 40px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12px;
margin-bottom: 30px;
color: white;
}
.hero-detail-header h2 {
font-size: 2.5rem;
margin-bottom: 10px;
}
.hero-detail-header .hero-id {
font-size: 1.2rem;
opacity: 0.9;
}
.form-group {
margin-bottom: 25px;
}
.form-group label {
display: block;
color: #555;
font-weight: 600;
margin-bottom: 8px;
font-size: 14px;
}
.form-group input,
.form-group textarea {
width: 100%;
padding: 12px 15px;
border: 2px solid #e0e0e0;
border-radius: 10px;
font-size: 15px;
font-family: inherit;
transition: all 0.3s;
}
.form-group input:focus,
.form-group textarea:focus {
outline: none;
border-color: #27ae60;
box-shadow: 0 0 0 3px rgba(39, 174, 96, 0.1);
}
.form-group textarea {
resize: vertical;
min-height: 100px;
}
.btn-group {
display: flex;
gap: 12px;
margin-top: 30px;
}
.btn {
padding: 14px 28px;
border: none;
border-radius: 10px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
flex: 1;
}
.btn-primary {
background: linear-gradient(135deg, #27ae60 0%, #229954 100%);
color: white;
box-shadow: 0 4px 12px rgba(39, 174, 96, 0.3);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(39, 174, 96, 0.4);
}
.btn-secondary {
background: #95a5a6;
color: white;
}
.btn-secondary:hover {
background: #7f8c8d;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: #999;
}
.empty-state h3 {
font-size: 1.5rem;
color: #666;
margin-bottom: 10px;
}
.empty-state p {
font-size: 1rem;
}
/* Stats */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: white;
padding: 25px;
border-radius: 12px;
text-align: center;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.stat-number {
font-size: 2.5rem;
font-weight: 700;
color: #27ae60;
margin-bottom: 8px;
}
.stat-label {
color: #7f8c8d;
font-size: 1rem;
font-weight: 600;
}
@media (max-width: 768px) {
.header h1 {
font-size: 2rem;
}
.dashboard-grid {
grid-template-columns: 1fr;
}
.hero-item {
flex-direction: column;
text-align: center;
}
.hero-actions {
width: 100%;
justify-content: center;
}
.btn-group {
flex-direction: column;
}
}
</style>
</head>
<body ng-controller="HeroesController">
<div class="container">
<div class="header">
<h1>🦸 Tour of Heroes</h1>
<p>Manage your favorite superheroes</p>
</div>
<!-- Navigation -->
<div class="nav">
<button class="nav-btn" ng-class="{active: currentView === 'dashboard'}" ng-click="setView('dashboard')">
Dashboard
</button>
<button class="nav-btn" ng-class="{active: currentView === 'heroes'}" ng-click="setView('heroes')">
Heroes
</button>
</div>
<!-- Dashboard View -->
<div class="card" ng-if="currentView === 'dashboard'">
<h2 class="card-title">Top Heroes</h2>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-number">{{heroes.length}}</div>
<div class="stat-label">Total Heroes</div>
</div>
<div class="stat-card">
<div class="stat-number">{{getActiveMissions()}}</div>
<div class="stat-label">Active Missions</div>
</div>
<div class="stat-card">
<div class="stat-number">{{getTotalPower()}}</div>
<div class="stat-label">Combined Power</div>
</div>
</div>
<div class="dashboard-grid">
<div class="hero-card" ng-repeat="hero in heroes | limitTo:6" ng-click="viewHero(hero)">
<div class="hero-name">{{hero.name}}</div>
<div class="hero-power">Power Level: {{hero.power}}</div>
</div>
</div>
</div>
<!-- Heroes List View -->
<div class="card" ng-if="currentView === 'heroes' && !selectedHero && !addingHero">
<h2 class="card-title">My Heroes</h2>
<div class="search-box">
<input type="text"
placeholder="🔍 Search heroes..."
ng-model="searchText">
</div>
<button class="add-hero-btn" ng-click="startAddHero()">
➕ Add New Hero
</button>
<div class="heroes-list" ng-if="getFilteredHeroes().length > 0">
<div class="hero-item" ng-repeat="hero in getFilteredHeroes()">
<div class="hero-info" ng-click="viewHero(hero)">
<div class="hero-badge">{{hero.id}}</div>
<div class="hero-details">
<h3>{{hero.name}}</h3>
<p>{{hero.ability}} • Power: {{hero.power}}</p>
</div>
</div>
<div class="hero-actions">
<button class="action-btn edit" ng-click="editHero(hero); $event.stopPropagation()">
✏️ Edit
</button>
<button class="action-btn delete" ng-click="deleteHero(hero); $event.stopPropagation()">
🗑️ Delete
</button>
</div>
</div>
</div>
<div class="empty-state" ng-if="getFilteredHeroes().length === 0">
<h3>No heroes found</h3>
<p>Try adjusting your search or add a new hero</p>
</div>
</div>
<!-- Hero Detail/Edit View -->
<div class="card hero-detail" ng-if="selectedHero || addingHero">
<div class="hero-detail-header" ng-if="!addingHero">
<h2>{{selectedHero.name}}</h2>
<div class="hero-id">Hero ID: {{selectedHero.id}}</div>
</div>
<h2 class="card-title" ng-if="addingHero">Add New Hero</h2>
<h2 class="card-title" ng-if="!addingHero">Edit Hero Details</h2>
<form ng-submit="saveHero()">
<div class="form-group">
<label>Hero Name *</label>
<input type="text"
ng-model="heroForm.name"
placeholder="Enter hero name"
required>
</div>
<div class="form-group">
<label>Special Ability *</label>
<input type="text"
ng-model="heroForm.ability"
placeholder="Enter special ability"
required>
</div>
<div class="form-group">
<label>Power Level (1-100) *</label>
<input type="number"
ng-model="heroForm.power"
min="1"
max="100"
placeholder="Enter power level"
required>
</div>
<div class="form-group">
<label>Origin Story</label>
<textarea ng-model="heroForm.origin"
placeholder="Tell us about this hero's origin..."></textarea>
</div>
<div class="btn-group">
<button type="submit" class="btn btn-primary">
💾 Save Hero
</button>
<button type="button" class="btn btn-secondary" ng-click="cancelEdit()">
❌ Cancel
</button>
</div>
</form>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
<script>
angular.module('heroesApp', [])
.controller('HeroesController', ['$scope', function($scope) {
// Initial heroes data
$scope.heroes = [
{ id: 1, name: 'Superman', ability: 'Super Strength & Flight', power: 95, origin: 'Born on planet Krypton' },
{ id: 2, name: 'Wonder Woman', ability: 'Combat & Lasso of Truth', power: 90, origin: 'Amazonian warrior princess' },
{ id: 3, name: 'Batman', ability: 'Intelligence & Gadgets', power: 85, origin: 'Witnessed parents murder' },
{ id: 4, name: 'The Flash', ability: 'Super Speed', power: 88, origin: 'Struck by lightning' },
{ id: 5, name: 'Spider-Man', ability: 'Web-Slinging & Spider Sense', power: 82, origin: 'Bitten by radioactive spider' },
{ id: 6, name: 'Iron Man', ability: 'Powered Armor & Technology', power: 87, origin: 'Genius billionaire inventor' },
{ id: 7, name: 'Thor', ability: 'Thunder God Powers', power: 93, origin: 'Norse god from Asgard' },
{ id: 8, name: 'Black Widow', ability: 'Espionage & Combat', power: 78, origin: 'Former Russian spy' },
{ id: 9, name: 'Captain America', ability: 'Enhanced Strength & Shield', power: 84, origin: 'Super soldier serum' },
{ id: 10, name: 'Hulk', ability: 'Incredible Strength', power: 96, origin: 'Gamma radiation accident' }
];
$scope.currentView = 'dashboard';
$scope.selectedHero = null;
$scope.addingHero = false;
$scope.searchText = '';
$scope.heroForm = {};
$scope.nextId = 11;
// View management
$scope.setView = function(view) {
$scope.currentView = view;
$scope.selectedHero = null;
$scope.addingHero = false;
$scope.searchText = '';
};
// View hero details
$scope.viewHero = function(hero) {
$scope.selectedHero = hero;
$scope.addingHero = false;
$scope.heroForm = angular.copy(hero);
};
// Start adding new hero
$scope.startAddHero = function() {
$scope.addingHero = true;
$scope.selectedHero = null;
$scope.heroForm = {
id: $scope.nextId,
name: '',
ability: '',
power: 50,
origin: ''
};
};
// Edit hero
$scope.editHero = function(hero) {
$scope.selectedHero = hero;
$scope.addingHero = false;
$scope.heroForm = angular.copy(hero);
};
// Save hero
$scope.saveHero = function() {
if ($scope.addingHero) {
// Add new hero
$scope.heroes.push(angular.copy($scope.heroForm));
$scope.nextId++;
} else {
// Update existing hero
var index = $scope.heroes.findIndex(function(h) {
return h.id === $scope.selectedHero.id;
});
if (index !== -1) {
$scope.heroes[index] = angular.copy($scope.heroForm);
}
}
$scope.cancelEdit();
};
// Cancel edit
$scope.cancelEdit = function() {
$scope.selectedHero = null;
$scope.addingHero = false;
$scope.heroForm = {};
};
// Delete hero
$scope.deleteHero = function(hero) {
if (confirm('Are you sure you want to delete ' + hero.name + '?')) {
var index = $scope.heroes.indexOf(hero);
if (index !== -1) {
$scope.heroes.splice(index, 1);
}
}
};
// Filter heroes
$scope.getFilteredHeroes = function() {
if (!$scope.searchText) {
return $scope.heroes;
}
var search = $scope.searchText.toLowerCase();
return $scope.heroes.filter(function(hero) {
return hero.name.toLowerCase().includes(search) ||
hero.ability.toLowerCase().includes(search);
});
};
// Stats
$scope.getActiveMissions = function() {
return Math.floor($scope.heroes.length * 0.6);
};
$scope.getTotalPower = function() {
return $scope.heroes.reduce(function(sum, hero) {
return sum + hero.power;
}, 0);
};
}]);
</script>
</body>
</html>
No comments yet. Be the first!