diff --git a/README.md b/README.md
index 7fad321..35f2f07 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,50 @@
-# front-end-coding-challenge
\ No newline at end of file
+Front-End Coding Challenge
+===
+
+### Premise
+It’s not always easy to evaluate a candidates technical abilities. While we love writing tests here, we hate asking people to take them during an interview. More importantly, we hate asking people to “white-board” solutions.
+
+We—as an industry—can do better. So, just how can we do better?
+
+### Solution
+We developed this coding challenge to help us better gauge your technical abilities. This challenge:
+
+* can be completed on your own time
+* where you are most comfortable
+* on equipment you’re accustomed to
+* using the tools you’re proficient with
+
+Basically, you have access to all the things that allow you to work best.
+
+We hope you’ll enjoy working through this challenge as much as we’ve enjoyed creating it for you.
+
+### Guidelines
+Because we’re awful developers, this project isn’t quite ready to release. There are some broken pieces that we’d like you to fix. To do that, you’ll need to:
+
+1. Fork this project.
+2. Fix any problems you find with it.
+3. Open a PR with your changes.
+4. You have 24-hours.
+
+Our awesome design team provided the following images to demonstrate what the site should look like: http://imgur.com/a/dDuKc
+
+### FAQ
+**Q: What am I supposed to focus on?**
+
+A: The entire project. There are problems with everything, it’s up to you to find them and fix them.
+
+**Q: How do I know if I’ve completed the challenge?**
+
+A: You don’t. But, that’s because _we_ don’t know either.
+
+**Q: I can’t get past X, what do I do?**
+
+A: Google is your friend.
+
+**Q: How long should this take?**
+
+A: That really depends on you.
+
+**Q: Can I rewrite this in React?**
+
+A: While React is a very popular library today, all of our code is written in Angular and we'd like to see your competancy with our stack.
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..913c70a
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,34 @@
+{
+ "name": "frontend-developer-challenge",
+ "description": "Sheesh. There's a lot wrong with this project.",
+ "main": "index.js",
+ "authors": [
+ "Ryan Provost"
+ ],
+ "license": "ISC",
+ "keywords": [
+ "frontend",
+ "challenge"
+ ],
+ "homepage": "https://github.com/rprovost/code-test-creation",
+ "private": true,
+ "ignore": [
+ "**/.*",
+ "node_modules",
+ "bower_components",
+ "test",
+ "tests"
+ ],
+ "dependencies": {
+ "angular": "^1.5.7",
+ "font-awesome": "fontawesome#^4.6.3",
+ "weather-icons": "^2.0.10",
+ "express": "^4.14.0"
+ },
+ "devDependencies": {
+ "angular-mocks": "^1.5.7",
+ "hammerjs": "^2.0.8",
+ "d3": "^4.2.2",
+ "react": "^15.3.0"
+ }
+}
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100644
index 0000000..572db9e
--- /dev/null
+++ b/gulpfile.js
@@ -0,0 +1,144 @@
+'use strict';
+
+const gulp = require('gulp');
+const pkg = require('./package.json');
+const plugins = require('gulp-load-plugins')();
+const browserSync = require('browser-sync').create();
+const karmaServer = require('karma').Server;
+
+gulp.task('build', ['build:html', 'build:js', 'build:json', 'build:sass', 'build:libs']);
+gulp.task('watch2', ['build', 'watch:tests', 'watch:src', 'serve']);
+
+/***
+ * Output a summary of unit test code coverage
+ **/
+gulp.task('test:coverage', (done) => {
+ new KarmaServer({
+ configFile: __dirname + '/karma.conf.js',
+singleRun: true,
+reporters: ['coverage'],
+coverageReporter: {
+ type: ['text-summary']
+ }
+ }, done).start();
+
+ return;
+});
+
+/***
+ * Generate an HTML report that details coverage
+ **/
+gulp.task('test:report', (done) => {
+ new karmaServer({
+ configFile: __dirname + '/karma.conf.js',
+ singleRun: true,
+ reporters: ['coverage'],
+ coverageReporter: {
+ type: ['html']
+ }
+ }, done).start();
+
+ return;
+});
+
+/***
+ * Run the project with BrowserSync
+ **/
+gulp.task('serve', () => {
+ browserSync.init({
+ server: {
+ baseDir: "./dist"
+ }
+ });
+
+ gulp.watch(['./dist/**/*.js', './dist/**/*.html'], browserSync.reload);
+});
+
+/***
+ * Reload or stream changes to browsers with BrowserSync
+ **/
+gulp.task('watch:src', () => {
+ gulp.watch('./src/**/*.scss', ['build:sass']);
+ gulp.watch('./src/**/*.js', ['lint', 'build:js'], browserSync.reload);
+ gulp.watch('./src/**/*.json', ['build:json'], browserSync.reload);
+ gulp.watch('./src/**/*.html', ['build:html'], browserSync.reload);
+});
+
+/***
+ * Run test suite when affected source files change
+ **/
+gulp.task('watch:tests',
+ function (done) {
+ new karmaServer({
+ configFile: __dirname + '/karma.conf.js',
+ singleRun: false,
+ reporters: ['progress'],
+ }, Done).start();
+});
+
+/***
+ * ESLint
+ **/
+gulp.task(
+ 'lint', () => {
+ let eslint = plugins.eslint;
+
+ gulp.src(['./src/**/*.js'])
+ .pipe(eslint())
+ .pipe(eslint.format())
+ .pipe(eslint.failAfterError());
+});
+
+/***
+ * Copy the HTML files into the DIST folder
+ **/
+gulp.task('build:html', () => {
+ gulp.src('./src/**/*.html')
+ .pipe(gulp.dest('./dist'));
+});
+
+/***
+ * Copy the JSON files into the DIST folder
+ **/
+gulp.task('build:json', () => {
+ gulp.src('./src/**/*.json')
+ .pipe(gulp.dest('./dist'));
+});
+
+/***
+ * Build all Sass files into a single css file
+ **/
+gulp.task('build:sass', () => {
+ gulp.src('./src/app.scss')
+ .pipe(plugins.sass.sync())
+ .pipe(plugins.rename(pkg.name + '.css'))
+ .pipe(gulp.dest('./dist'))
+ .pipe(browserSync.stream());
+});
+
+/***
+ * Build all JS files into a single application file
+ **/
+gulp.task('build:js', () => {
+ let filename = pkg.name + '.js';
+ let filenameMinified = pkg.name + '.min.js';
+
+ gulp.src(['!./src/**/*.spec.js', './src/**/*.js'])
+ // .pipe(plugins.plumber())
+
+ .pipe(plugins.concat(filename))
+ .pipe(gulp.dest('./dist'))
+});
+
+/***
+ * Copy all dependencies into the DIST folder
+ **/
+gulp.task('build:libs', () => {
+ gulp.src('./bower.json')
+ .pipe(plugins.mainBowerFiles({
+ overrides: {
+ "font-awesome": { main: ['./css/font-awesome.css'] }
+ }
+ }))
+ .pipe(gulp.dest('./dist/libs'));
+});
diff --git a/karma.conf.js b/karma.conf.js
new file mode 100644
index 0000000..b6eaefd
--- /dev/null
+++ b/karma.conf.js
@@ -0,0 +1,16 @@
+module.exports = function(config) {
+ config.set({
+ browsers: ['Chrome'],
+ frameworks: ['jasmine'],
+ files: [
+ {pattern: 'src/**/*.json', included: false},
+ 'node_modules/karma-read-json/karma-read-json.js',
+ 'bower_components/angular/angular.js',
+ 'bower_components/angular-mocks/angular-mocks.js',
+ 'src/**/*.js'
+ ],
+ preprocessors: {
+ 'src/**/*.js': ['coverage']
+ },
+ });
+};
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..0c4fe52
--- /dev/null
+++ b/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "frontend-developer-challenge",
+ "version": "1.0.0",
+ "description": "Sheesh. There's a lot wrong with this project.",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/rprovost/code-test-creation.git"
+ },
+ "keywords": [
+ "frontend",
+ "challenge"
+ ],
+ "author": "Ryan Provost",
+ "license": "ISC",
+ "bugs": {
+ "url": "https://github.com/rprovost/code-test-creation/issues"
+ },
+ "homepage": "https://github.com/rprovost/code-test-creation#readme",
+ "devDependencies": {
+ "browser-sync": "^2.13.0",
+ "express": "^4.14.0",
+ "gulp": "^3.9.1",
+ "gulp-concat": "^2.6.0",
+ "gulp-eslint": "^3.0.1",
+ "gulp-load-plugins": "^1.2.4",
+ "gulp-main-bower-files": "^1.5.2",
+ "gulp-plumber": "^1.1.0",
+ "gulp-rename": "^1.2.2",
+ "gulp-sass": "^2.3.2",
+ "istanbul": "^0.4.4",
+ "jasmine-core": "^2.4.1",
+ "karma": "^1.1.1",
+ "karma-chrome-launcher": "^1.0.1",
+ "karma-coverage": "^1.1.1",
+ "karma-coverage-es6": "^0.2.7",
+ "karma-jasmine": "^1.0.2",
+ "karma-phantomjs-launcher": "^1.0.1",
+ "karma-read-json": "^1.1.0"
+ },
+ "dependencies": {}
+}
diff --git a/src/app.js b/src/app.js
new file mode 100644
index 0000000..30945e6
--- /dev/null
+++ b/src/app.js
@@ -0,0 +1,68 @@
+angular.module('weather', [])
+
+.controller('forecast', ['$http', '$scope', function($http, $scope){
+
+ const _this = this;
+ let default_city = 'New York, NY';
+
+ // recall the existing city or display the default
+ $scope.name = $scope.name || default_city;
+
+ // holds the 48 conditions and their corresponding icons
+ _this.conditions = new Array(12);
+
+ /***
+ * there are 48 different condition codes that the api can return
+ * this method makes those conditions and their corresponding icon mapping
+ * available to the rest of the controller
+ **/
+ var GetConditionMap = function() {
+ var success = function(response) {
+ _this.conditions = response.data;
+ };
+
+ const error = function(response) {
+
+ };
+
+ $http.get('conditions.json').then(success, error);
+ }
+
+ /***
+ * this method is expected to set the forecast objects
+ **/
+ $scope.getWeather = function(city) {
+ var success = function(response) {
+ var data = response.data.query.results.channel;
+
+ $scope.location = data.location;
+ $scope.item = data.item;
+ };
+
+ let Error = function(response) {
+ };
+
+ $http.get(`http://query.yahooapis.com/v1/public/yql?q=select * from weather.forecast where woeid in (select woeid from geo.places(1) where text="${city}")&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys`)
+ .then(success, Error);
+ }
+
+ /***
+ * this method is expected to return the icon for the corresponding condition code
+ * from the codeToCondition map file
+ **/
+ $scope.getIcon = function(code) {
+ return _this.conditions.filter(condition => condition.code == code)[0].icon;
+ };
+
+ /***
+ * load the condition code map
+ * this maps the condition code returned from the api to the corresponding icon
+ */
+ setTimeout(GetConditionMap,
+ 5000);
+
+ /***
+ * load weather for the default city
+ */
+ // $scope.getWeather(name);
+}]);
diff --git a/src/app.scss b/src/app.scss
new file mode 100644
index 0000000..8f9fda8
--- /dev/null
+++ b/src/app.scss
@@ -0,0 +1,4 @@
+@import "styles/colors";
+@import "styles/page"
+@import "styles/menu";
+@import "styles/weather";
diff --git a/src/app.spec.js b/src/app.spec.js
new file mode 100644
index 0000000..c6a8fd0
--- /dev/null
+++ b/src/app.spec.js
@@ -0,0 +1,116 @@
+// testing controller
+describe('The weather app', function() {
+ var $httpBackend,
+ $scope,
+;
+
+ // Set up the module
+ beforeEach(module('weather'));
+
+ beforeEach(inject(function($injector) {
+ $httpBackend = $injector.get('$httpBackend');
+ $scope = $injector.get('$rootScope');
+
+ var $controller = $injector.get('$controller');
+
+ createController = function() {
+ return $controller('forecast', {'$scope' : $scope });
+ };
+ }));
+
+ afterEach(function() {
+ $httpBackend.verifyNoOutstandingExpectation();
+ $httpBackend.verifyNoOutstandingRequest();
+ });
+
+ describe('condition map', function(){
+ beforeEach(function(){
+ });
+
+ it('should throw an error when the condition map cannot be loaded.', function(){
+ $httpBackend.whenGET(/conditions/).respond(200, {});
+ $httpBackend.whenGET(/query.yahooapis.com/).respond(200, readJSON('./src/forecast.json'));
+
+ var controller = createController();
+
+ expect(controller.conditions).not.toBe(undefined);
+ expect( controller.conditions.length ).toBe(0);
+
+
+ expect(controller.conditions.length).toBe(0);
+ expect(controller.conditions[1]).toBe(undefined);
+ });
+
+ it('should load all known conditions.', function(){
+ $httpBackend.whenGET(/conditions/).respond(200, readJSON('./src/conditions.json'));
+ $httpBackend.whenGET(/query.yahooapis.com/).respond(200, readJSON('./src/forecast.json'));
+
+ var controller = createController();
+
+ expect(controller.conditions).not.toBe(undefined);
+ expect(controller.conditions.length).toBe(0);
+
+ $httpBackend.flush();
+
+ expect(controller.conditions.length).toBe(49);
+ expect(controller.conditions[1].code).toBe(1);
+ expect(controller.conditions[1].icon).toBe('wi-storm-showers');
+ });
+ });
+
+ describe('with condition map successfully loaded', function() {
+ beforeEach(function(){
+ });
+
+ it('should gracefully handle a weather forecast that cannot be loaded.', function() {
+
+ $httpBackend.whenGET(/query.yahooapis.com/).respond(500, {});
+
+ var controller = createController();
+
+ $httpBackend.flush() ;
+
+ expect($scope.item).toBe(undefined);
+ expect($scope.location).toBe(undefined);
+
+ });
+
+ it('should load a weather forecast for a default location.', function() {
+
+ $httpBackend.whenGET(/query.yahooapis.com/).respond(200, readJSON('./src/forecast.json'));
+
+ var controller = createController();
+
+ expect($scope.item).toBe(undefined);
+ expect($scope.location).toBe(undefined);
+
+ $httpBackend.flush();
+
+ expect( $scope.item ).not.toBe(undefined);
+ expect($scope.location).not.toBe(undefined);
+
+ expect($scope.location.city).toBe('New York');
+ // expect($scope.item.condition.code).toBe('29');
+
+ });
+ });
+
+ describe('with condition map and forecast successfully loaded', function() {
+ beforeEach(function(){
+ $httpBackend.whenGET(/conditions/).respond(200, readJSON('./src/conditions.json'));
+ $httpBackend.whenGET(/query.yahooapis.com/).respond(readJSON('./src/forecast.json'));
+
+ var controller = createController();
+
+ $httpBackend.flush();
+ });
+
+
+
+
+ it('should provide an icon for the provided weather code.', function(){
+ var icon = $scope.getIcon('29');
+ expect(icon).toBe('wi-night-partly-cloudy');
+ });
+ });
+});
\ No newline at end of file
diff --git a/src/conditions.json b/src/conditions.json
new file mode 100644
index 0000000..dd5d609
--- /dev/null
+++ b/src/conditions.json
@@ -0,0 +1,51 @@
+[
+ { "code": 0, "icon": "wi-tornado", "text": "tornado" },
+ { "code": 1, "icon": "wi-storm-showers", "text": "tropical storm" },
+ { "code": 2, "icon": "wi-hurricane", "text": "hurricane" },
+ { "code": 3, "icon": "wi-thunderstorm", "text": "severe thunderstorms" },
+ { "code": 4, "icon": "wi-day-sunny", "text": "thunderstorms" },
+ { "code": 5, "icon": "wi-sleet", "text": "mixed rain and snow" },
+ { "code": 6, "icon": "wi-sleet", "text": "mixed rain and sleet" },
+ { "code": 7, "icon": "wi-sleet", "text": "mixed snow and sleet" },
+ { "code": 8, "icon": "wi-rain-mix", "text": "freezing drizzle" },
+ { "code": 9, "icon": "wi-rain", "text": "drizzle" },
+ { "code": 10, "icon": "wi-rain-mix", "text": "freezing rain" },
+ { "code": 11, "icon": "wi-showers", "text": "showers" },
+ { "code": 12, "icon": "wi-showers", "text": "showers" },
+ { "code": 13, "icon": "wi-snow", "text": "snow flurries" },
+ { "code": 14, "icon": "wi-snow", "text": "light snow showers" },
+ { "code": 15, "icon": "wi-snow", "text": "blowing snow" },
+ { "code": 16, "icon": "wi-snow", "text": "snow" },
+ { "code": 17, "icon": "wi-hail", "text": "hail" },
+ { "code": 18, "icon": "wi-sleet", "text": "sleet" },
+ { "code": 19, "icon": "wi-dust", "text": "dust" },
+ { "code": 20, "icon": "wi-fog", "text": "foggy" },
+ { "code": 21, "icon": "wi-day-haze", "text": "haze" },
+ { "code": 22, "icon": "wi-smoke", "text": "smoky" },
+ { "code": 23, "icon": "wi-day-windy", "text": "blustery" },
+ { "code": 24, "icon": "wi-day-windy", "text": "windy" },
+ { "code": 25, "icon": "wi-thermometer-exterior", "text": "cold" },
+ { "code": 26, "icon": "wi-cloudy", "text": "cloudy" },
+ { "code": 27, "icon": "wi-night-cloudy", "text": "mostly cloudy (night)" },
+ { "code": 28, "icon": "wi-day-cloudy", "text": "mostly cloudy (day)" },
+ { "code": 29, "icon": "wi-night-partly-cloudy", "text": "partly cloudy (night)" },
+ { "code": 30, "icon": "wi-day-cloudy", "text": "partly cloudy (day)" },
+ { "code": 31, "icon": "wi-night-clear", "text": "clear (night)" },
+ { "code": 32, "icon": "wi-day-sunny", "text": "sunny" },
+ { "code": 33, "icon": "", "text": "fair (night)" },
+ { "code": 34, "icon": "", "text": "fair (day)" },
+ { "code": 35, "icon": "wi-day-rain-mix", "text": "mixed rain and hail" },
+ { "code": 36, "icon": "wi-hot", "text": "hot" },
+ { "code": 37, "icon": "wi-thunderstorm", "text": "isolated thunderstorms" },
+ { "code": 38, "icon": "wi-thunderstorm", "text": "scattered thunderstorms" },
+ { "code": 39, "icon": "wi-thunderstorm", "text": "scattered thunderstorms" },
+ { "code": 40, "icon": "wi-thunderstorm", "text": "scattered showers" },
+ { "code": 41, "icon": "wi-snow", "text": "heavy snow" },
+ { "code": 42, "icon": "wi-snow", "text": "scattered snow showers" },
+ { "code": 43, "icon": "wi-snow", "text": "heavy snow" },
+ { "code": 44, "icon": "wi-cloud", "text": "partly cloudy" },
+ { "code": 45, "icon": "wi-storm-showers", "text": "thundershowers" },
+ { "code": 46, "icon": "wi-snow", "text": "snow showers" },
+ { "code": 47, "icon": "wi-thunderstorm", "text": "isolated thundershowers" },
+ { "code": 3200, "icon": "wi-na", "text": "not available" }
+]
\ No newline at end of file
diff --git a/src/forecast.json b/src/forecast.json
new file mode 100644
index 0000000..86fd7a8
--- /dev/null
+++ b/src/forecast.json
@@ -0,0 +1,138 @@
+{
+ "query": {
+ "count": 1,
+ "created": "2016-07-13T02:37:36Z",
+ "lang": "en-US",
+ "results": {
+ "channel": {
+ "units": {
+ "distance": "mi",
+ "pressure": "in",
+ "speed": "mph",
+ "temperature": "F"
+ },
+ "title": "Yahoo! Weather - New York, NY, US",
+ "link": "http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-2459115/",
+ "description": "Yahoo! Weather for New York, NY, US",
+ "language": "en-us",
+ "lastBuildDate": "Tue, 12 Jul 2016 10:37 PM EDT",
+ "ttl": "60",
+ "location": {
+ "city": "New York",
+ "country": "United States",
+ "region": " NY"
+ },
+ "wind": {
+ "chill": "75",
+ "direction": "180",
+ "speed": "22"
+ },
+ "atmosphere": {
+ "humidity": "73",
+ "pressure": "1016.0",
+ "rising": "0",
+ "visibility": "16.1"
+ },
+ "astronomy": {
+ "sunrise": "5:36 am",
+ "sunset": "8:27 pm"
+ },
+ "image": {
+ "title": "Yahoo! Weather",
+ "width": "142",
+ "height": "18",
+ "link": "http://weather.yahoo.com",
+ "url": "http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-wea.gif"
+ },
+ "item": {
+ "title": "Conditions for New York, NY, US at 09:00 PM EDT",
+ "lat": "40.71455",
+ "long": "-74.007118",
+ "link": "http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-2459115/",
+ "pubDate": "Tue, 12 Jul 2016 09:00 PM EDT",
+ "condition": {
+ "code": "29",
+ "date": "Tue, 12 Jul 2016 09:00 PM EDT",
+ "temp": "75",
+ "text": "Partly Cloudy"
+ },
+ "forecast": [{
+ "code": "30",
+ "date": "12 Jul 2016",
+ "day": "Tue",
+ "high": "81",
+ "low": "69",
+ "text": "Partly Cloudy"
+ }, {
+ "code": "47",
+ "date": "13 Jul 2016",
+ "day": "Wed",
+ "high": "83",
+ "low": "71",
+ "text": "Scattered Thunderstorms"
+ }, {
+ "code": "4",
+ "date": "14 Jul 2016",
+ "day": "Thu",
+ "high": "85",
+ "low": "74",
+ "text": "Thunderstorms"
+ }, {
+ "code": "4",
+ "date": "15 Jul 2016",
+ "day": "Fri",
+ "high": "92",
+ "low": "77",
+ "text": "Thunderstorms"
+ }, {
+ "code": "30",
+ "date": "16 Jul 2016",
+ "day": "Sat",
+ "high": "85",
+ "low": "75",
+ "text": "Partly Cloudy"
+ }, {
+ "code": "30",
+ "date": "17 Jul 2016",
+ "day": "Sun",
+ "high": "87",
+ "low": "75",
+ "text": "Partly Cloudy"
+ }, {
+ "code": "4",
+ "date": "18 Jul 2016",
+ "day": "Mon",
+ "high": "85",
+ "low": "74",
+ "text": "Thunderstorms"
+ }, {
+ "code": "4",
+ "date": "19 Jul 2016",
+ "day": "Tue",
+ "high": "85",
+ "low": "74",
+ "text": "Thunderstorms"
+ }, {
+ "code": "4",
+ "date": "20 Jul 2016",
+ "day": "Wed",
+ "high": "84",
+ "low": "73",
+ "text": "Thunderstorms"
+ }, {
+ "code": "30",
+ "date": "21 Jul 2016",
+ "day": "Thu",
+ "high": "84",
+ "low": "73",
+ "text": "Partly Cloudy"
+ }],
+ "description": "\n
\nCurrent Conditions:\n
Partly Cloudy\n
\n
\nForecast:\n
Tue - Partly Cloudy. High: 81Low: 69\n
Wed - Scattered Thunderstorms. High: 83Low: 71\n
Thu - Thunderstorms. High: 85Low: 74\n
Fri - Thunderstorms. High: 92Low: 77\n
Sat - Partly Cloudy. High: 85Low: 75\n
\n
\nFull Forecast at Yahoo! Weather\n
\n
\n(provided by The Weather Channel)\n
\n]]>",
+ "guid": {
+ "isPermaLink": "false"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/index.html b/src/index.html
new file mode 100644
index 0000000..a73ac41
--- /dev/null
+++ b/src/index.html
@@ -0,0 +1,76 @@
+
+
+