diff --git a/content.js b/content.js
new file mode 100644
index 0000000000000000000000000000000000000000..34b58c79c1b4cb7e96581a128a74ffd454186605
--- /dev/null
+++ b/content.js
@@ -0,0 +1,126 @@
+/* global module, require, console */
+/* jshint globalstrict: true */
+'use strict';
+
+let Content = {
+
+  click_for_points: {
+    instructions: "This is an example of how automatically graded assignments appear on this course. Notice that it is always crucial to <strong>carefully read assignment instruction</strong> before starting to solve it. At this time there is no problem to solve. You are just expected to click the button on left to receive your points on this assigment.",
+    html: "<button class=\"green\">Click Me!</button>",
+    selector: ".exercise button",
+    events: "click",
+    points: function ($element, config, event) {
+      return {
+        points: 10,
+        feedback: 'Congratulations!'
+      };
+    },
+    maxPoints: 10,
+    title: "Click for points",
+    description: "An example of how these assignments work",
+    concepts: ["Automatic assessment"],
+    order: 0
+  },
+
+  fix_typo: {
+    instructions: "On the left you see a message that unfortunately contains a typo. Your task is to open the browser inspector tool, find the paragraph element <strong>&lt;p&gt;</strong> containing the message and then edit the element body to correctly contain the intended message \"<strong>Hello World!</strong>\".",
+    html: "<div class=\"acos-webdev-poi\"><p>Hello Wordl!</p></div>",
+    mutations: true,
+    points: function ($element, config, mutations) {
+      let $p = $element.find('.exercise p');
+      if ($p.length != 1) {
+        return {
+          points: 0,
+          feedback: 'The document is altered beyond repair. Avoid accidentally removing nodes. You should reset the assignment to start over.'
+        };
+      } else if ($p.eq(0).text() != 'Hello Wordl!') {
+        if ($p.eq(0).text() != 'Hello World!') {
+          return {
+            points: 5,
+            feedback: 'Almost there! You were able to change the node contents but the message is not exactly as requested.'
+          };
+        } else {
+          return {
+            points: 10,
+            feedback: 'Great work! You were able to change the node contents as requested.'
+          };
+        }
+      }
+      return undefined;
+    },
+    maxPoints: 10,
+    title: "Fix typo",
+    description: "Find and change element body",
+    concepts: ["Inspector", "DOM", "HTML"],
+    order: 1
+  },
+
+  change_color: {
+    instructions: "The blue area on left contains barely visible text. Your task is to use the browser inspector tool to locate the paragraph element <strong>&lt;p&gt;</strong> containing the barely visible text and then add a CSS property to apply another <strong>color</strong>. <em>Note that disabling current CSS rules does not grade the exercise.</em>",
+    html: "<div class=\"acos-webdev-poi\"><p class=\"blue\">The quick brown fox jumps over the lazy dog's back.</p></div>",
+    mutations: true,
+    points: function ($element, config, mutations) {
+      let $p = $element.find('.exercise p');
+      if ($p.length != 1) {
+        return {
+          points: 0,
+          feedback: 'The document is altered beyond repair. Avoid accidentally removing nodes. You should reset the assignment to start over.'
+        };
+      } else if (!config.graded && $p.eq(0).css('color') != 'rgb(173, 216, 230)') {
+        config.graded = true;
+        return {
+          points: 10,
+          feedback: 'Excellent! You changed the color of that text.'
+        };
+      }
+      return undefined;
+    },
+    maxPoints: 10,
+    title: "Change text color",
+    description: "Find and change text color using CSS",
+    concepts: ["Inspector", "DOM", "CSS"],
+    order: 2
+  },
+
+  disabled_button: {
+    instructions: "You need to click the button on left to receive your points on this assigment. Too bad that the button is disabled. Your task is to use the browser inspector tool to locate the <strong>button</strong>, remove the <strong>disabled</strong> attribute and finally click it.",
+    html: "<button class=\"green\" disabled>Click Me!</button>",
+    selector: ".exercise button",
+    events: "click",
+    points: function ($element, config, event) {
+      return {
+        points: 10,
+        feedback: 'Congratulations!'
+      };
+    },
+    maxPoints: 10,
+    title: "Disabled button",
+    description: "Find and enable button",
+    concepts: ["Inspector", "DOM", "HTML"],
+    order: 3
+  },
+
+  console_command: {
+    instructions: "This time the area on left does not have any visible content. However, this exercise defines a JavaScript function that can grant you points. Your task is to use the browser console to call the function like this: <code>giveMePoints();</code>",
+    html: "<div id=\"events\"></div>",
+    script: function giveMePoints() {
+      document.getElementById('events').dispatchEvent(new Event('grade'));
+    },
+    selector: "#events",
+    events: "grade",
+    points: function ($element, config, event) {
+      return {
+        points: 10,
+        feedback: 'Congratulations!'
+      };
+    },
+    maxPoints: 10,
+    title: "Console command",
+    description: "Call function in browser console",
+    concepts: ["Console", "JavaScript"],
+    order: 4
+  }
+
+};
+
+module.exports = Content;
diff --git a/content.json b/content.json
deleted file mode 100644
index b21a024ed7060eb109505834c7d48569cde9eeac..0000000000000000000000000000000000000000
--- a/content.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
-  "hidden_button": {
-    "html": "<div class=\"acos-webdev-poi\"><button class=\"green\" style=\"display:none\">Click me</button></div>",
-    "selector": ".exercise button",
-    "events": "click",
-    "instructions": "The blue area on left has a hidden button in the document object model. Your task is to use the web browser inspector to first display that button and then click it to receive your points.",
-    "maxPoints": 10,
-    "title": "Hidden button",
-    "description": "Find and display hidden button in browser inspector",
-    "concepts": ["Inspector", "DOM", "CSS"],
-    "order": 0
-  },
-  "delete_element": {
-    "html": "<ul><li class=\"acos-webdev-poi\">😀</li><li class=\"acos-webdev-poi angry-face\">😠</li><li class=\"acos-webdev-poi\">😀</li></ul>",
-    "selector": ".angry-face",
-    "events": "DOMNodeRemoved",
-    "instructions": "There are two happy and one angry face on left. Your task is to use the web browser inspector to delete the angry face from the document object model and keep the two happy faces.",
-    "maxPoints": 10,
-    "title": "Delete element",
-    "description": "Find and delete element in browser inspector",
-    "concepts": ["Inspector", "DOM"],
-    "order": 1
-  }
-}
diff --git a/index.js b/index.js
index 033d2a94cfcdf7a90f2a91d40f5dc05f743d6759..04174d4e4b18b2c7b88f90c7ff6328566e65cc3a 100644
--- a/index.js
+++ b/index.js
@@ -2,14 +2,33 @@
 /* jshint globalstrict: true */
 'use strict';
 let nj = require('nunjucks');
-let fs = require('fs');
 
-let AcosMod = function () {};
+let Package = function () {};
 
 let baseDir = __dirname;
-let content = JSON.parse(fs.readFileSync(baseDir + '/content.json', 'utf8'));
+let content = require('./content');
 
-AcosMod.initialize = function (req, params, handlers, cb) {
+Package.meta = {
+  'name': 'webdev-inspector',
+  'shortDescription': 'Exercise package using web browser inspector tool.',
+  'description': '',
+  'author': 'Lassi Haaranen & Teemu Lehtinen',
+  'license': 'MIT',
+  'version': '0.1.0',
+  'url': '',
+  'teaserContent': ['click_for_points', 'fix_typo'],
+  'contents': content
+};
+Package.packageType = 'content';
+Package.contentTypeNamespace = 'webdev';
+Package.namespace = 'webdev-inspector';
+
+Package.register = function (handlers, app, conf) {
+  handlers.contentPackages['webdev-inspector'] = Package;
+  handlers.contentTypes.webdev.installedContentPackages.push(Package);
+};
+
+Package.initialize = function (req, params, handlers, cb) {
   let templateDir = baseDir + '/templates/';
   nj.configure(templateDir, { autoescape: false });
 
@@ -23,33 +42,12 @@ AcosMod.initialize = function (req, params, handlers, cb) {
   let templateParam = {
     id: 'acos-webdev-inspector-' + params.name,
     config: JSON.stringify(config),
-    acceptTest: config.acceptTest,
-    pointsCalculator: config.pointsCalculator
+    script: typeof(config.script) == 'function' ? config.script.toString() : undefined,
+    points: typeof(config.points) == 'function' ? config.points.toString() : undefined
   };
   params.headContent += nj.render('head.html', templateParam);
   params.bodyContent += nj.render('body.html', templateParam);
   cb();
 };
 
-AcosMod.register = function (handlers, app, conf) {
-  handlers.contentPackages['webdev-inspector'] = AcosMod;
-  handlers.contentTypes.webdev.installedContentPackages.push(AcosMod);
-};
-
-AcosMod.namespace = 'webdev-inspector';
-AcosMod.contentTypeNamespace = 'webdev';
-AcosMod.packageType = 'content';
-
-AcosMod.meta = {
-  'name': 'webdev-inspector',
-  'shortDescription': 'Exercise package using web browser inspector tool.',
-  'description': '',
-  'author': 'Lassi Haaranen & Teemu Lehtinen',
-  'license': 'MIT',
-  'version': '0.1.0',
-  'url': '',
-  'teaserContent': ['hidden_button', 'delete_element'],
-  'contents': content
-};
-
-module.exports = AcosMod;
+module.exports = Package;
diff --git a/templates/head.html b/templates/head.html
index 4208da2109de8b0874ee16ccfa9b8f715c06b44e..995d19eac31dc1ab6805a73946e6f5df937a4de1 100644
--- a/templates/head.html
+++ b/templates/head.html
@@ -3,8 +3,8 @@
     new ACOSWebdev(
       $('#{{ id }}'),
       {{ config }},
-      {% if acceptTest %}{{ acceptTest }}{% else %}undefined{% endif %},
-      {% if pointsCalculator %}{{ pointsCalculator }}{% else %}undefined{% endif %}
+      {% if points %}{{ points }}{% else %}undefined{% endif %}
     );
   });
+  {% if script %}{{ script }}{% endif %}
 </script>
diff --git a/tests/content-tests.js b/tests/content-tests.js
index d66c3884dfc1c259c31f497e6a7bc13ce44302a5..452f5ad8b7ad4152032942d25a1fc06635214119 100644
--- a/tests/content-tests.js
+++ b/tests/content-tests.js
@@ -18,39 +18,53 @@ describe('Content module acos-webdev-inspector', () => {
 
   });
 
-  describe('Exercise hidden_button', () => {
+  describe('Exercise fix_typo', () => {
 
     it('exercise page should open', () => {
-      browser.url('/html/webdev/webdev-inspector/hidden_button');
+      browser.url('/html/webdev/webdev-inspector/fix_typo');
     });
 
     it('should start with zero grade', () => {
       let points = browser.$$('.guide-column .points');
       points.should.have.lengthOf(1);
-      points[0].getText().should.equal('0 / 10 p.');
+      points[0].getText().should.contain('0 / 10');
+      points[0].getText().should.not.contain('10 / 10');
     });
 
-    it('should include hidden button', () => {
-      let buttons = browser.$$('.exercise button');
-      buttons.should.have.lengthOf(1);
-      buttons[0].isDisplayed().should.be.false;
+    it('should include text paragraph', () => {
+      let p = browser.$$('.exercise p');
+      p.should.have.lengthOf(1);
+      p[0].getText().should.equal('Hello Wordl!');
     });
 
-    it('button should be set visible and then clicked', () => {
+    it('change text', () => {
       browser.execute(() => {
-        document.querySelector('.exercise button').removeAttribute('style')
+        document.querySelector('.exercise p').textContent = 'Hello!';
       });
-      let button = browser.$('.exercise button');
-      button.isDisplayed().should.be.true;
-      button.click();
-      browser.waitUntil(() => $$('.explosion').length == 0, 4000, 'expected explosion effect pass');
+      browser.waitUntil(() => $$('.explosion').length == 0, 4000, 'expected explosion effect to pass');
+    });
+
+    it('should now display half grade', () => {
+      let points = browser.$$('.guide-column .points');
+      points.should.have.lengthOf(1);
+      points[0].getText().should.contain('5 / 10');
+      points[0].getAttribute('class').should.contain('yellow');
+      points[0].getAttribute('class').should.not.contain('green');
+    });
+
+    it('fix text', () => {
+      browser.execute(() => {
+        document.querySelector('.exercise p').textContent = 'Hello World!';
+      });
+      browser.waitUntil(() => $$('.explosion').length == 0, 4000, 'expected explosion effect to pass');
     });
 
     it('should now display full grade', () => {
       let points = browser.$$('.guide-column .points');
       points.should.have.lengthOf(1);
-      points[0].getText().should.equal('10 / 10 p.');
+      points[0].getText().should.contain('10 / 10');
       points[0].getAttribute('class').should.contain('green');
+      points[0].getAttribute('class').should.not.contain('yellow');
     });
 
     it('reset button should exist and be clicked', () => {
@@ -63,12 +77,13 @@ describe('Content module acos-webdev-inspector', () => {
     });
 
     it('state should have now reset to initial', () => {
-      let buttons = browser.$$('.exercise button');
-      buttons.should.have.lengthOf(1);
-      buttons[0].isDisplayed().should.be.false;
+      let p = browser.$$('.exercise p');
+      p.should.have.lengthOf(1);
+      p[0].getText().should.equal('Hello Wordl!');
       let points = browser.$$('.guide-column .points');
       points.should.have.lengthOf(1);
-      points[0].getText().should.equal('0 / 10 p.');
+      points[0].getText().should.contain('0 / 10');
+      points[0].getText().should.not.contain('10 / 10');
       points[0].getAttribute('class').should.not.contain('green');
     });