diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..6ff898e
Binary files /dev/null and b/.DS_Store differ
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1b503c7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,73 @@
+## Dynamic Content Delivery
+
+A static website that returns appropriate content to the website user based on the provided ID.
+
+## Which files am I allowed to edit?
+- [template section of index.html](index.html)
+- all files in `data` directory:
+ - [constants.js](data/constants.js)
+ - [custom-style.css](data/custom-style.css)
+
+## How to use
+Firstly, create template for your webpage. Go to [index.html](index.html) and edit code **only between these lines**:
+```html
+
+
+
+```
+This is the **template section** of the HTML file.
+
+Example:
+```html
+
+
Hi!
+
+```
+
+Then, prepare your API:
+1. Clone [this](https://github.com/sadorowo/dynamic-content-delivery-api) repository.
+2. Edit [data/data-source.json] file:
+ - provide your data in `DATA` constant like this:
+ ```javascript
+ const DATA = {
+ "id1": {
+ "#name": {
+ "textContent": "John",
+ "style": {
+ "color": "red"
+ }
+ }
+ },
+ "id2": {
+ "#name": {
+ "textContent": "Anna",
+ "style": {
+ "color": "blue"
+ }
+ }
+ },
+ ...
+ }
+ ```
+ - edit your [data/config.json]:
+ 1. provide your API host in `API_HOST` constant.
+ 2. provide your API port in `PORT` constant.
+
+ > Warning!
+ > Do not change the structure of the `DATA` object. It must be an object with keys being IDs and values, which are objects with CSS selectors as keys and objects with DOM properties as values.
+ > Make sure that your API is running on the same port as the one provided in `PORT` constant.
+
+3. Deploy your API:
+ 1. Install Node.js and npm.
+ 2. Run `npm install` in the root directory of the API.
+ 3. Run `npm start` in the root directory of the API.
+
+## Custom styles
+This website supports custom CSS. Just edit [this](data/custom-style.css) file.
+If provided style doesn't work, try adding `!important` to the end of CSS rule.
+
+## License
+This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
+
+## Authors
+- [**@sadorowo**](https://github.com/sadorowo) - original author
\ No newline at end of file
diff --git a/data/constants.js b/data/constants.js
new file mode 100644
index 0000000..2e04ef6
--- /dev/null
+++ b/data/constants.js
@@ -0,0 +1,11 @@
+const API_HOST = 'http://localhost';
+const API_VERSION = 1;
+const API_PORT = 3000;
+
+const prepareUrl = (...urls) => `${API_HOST}:${API_PORT}/api/v${API_VERSION}/${urls.join('/')}`;
+
+// Leave this unchanged, if you haven't modified anything in API route configuration.
+const ROUTES = {
+ GET_RESOURCE: (id) => prepareUrl('resource', id),
+ STATUS: prepareUrl('status')
+}
\ No newline at end of file
diff --git a/data/custom-style.css b/data/custom-style.css
new file mode 100644
index 0000000..8021321
--- /dev/null
+++ b/data/custom-style.css
@@ -0,0 +1 @@
+/* If rule statements doesn't work, add !important to the end of it. */
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..c32b52d
--- /dev/null
+++ b/index.html
@@ -0,0 +1,23 @@
+
+
+
+ Content Delivery Network
+
+
+
+
+
+
+
+
+
Enter your access code:
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/scripts/script.js b/scripts/script.js
new file mode 100644
index 0000000..e1c8ed6
--- /dev/null
+++ b/scripts/script.js
@@ -0,0 +1,54 @@
+const startPage = document.getElementById('internal__start-page');
+const templatePage = document.getElementById('internal__template');
+
+const wait = (time) => new Promise(resolve => setTimeout(resolve, time * 1000));
+
+// Check if API is alive
+fetchStatus()
+ .catch(() => alert('API is down or an error occurred.'));
+
+if (!startPage || !templatePage) {
+ alert("Base website template got corrupted! Can't find either start page or template page. Closing the window.");
+ window.close();
+} else {
+ async function proceed() {
+ const input = document.querySelector('#internal__start-page > input');
+ const patches = await fetchResource(input.value);
+
+ if (!patches) return alert('Bad access code!');
+ applyPatches(patches);
+ await fadeBlock();
+ }
+
+ function applyPatches(patches) {
+ for (const [key, object] of Object.entries(patches)) {
+ const element = document.querySelector(key);
+ if (!element) {
+ alert(`Bad data source template! No HTML element found for selector '${key}'.`);
+ continue;
+ }
+
+ parsePatch(element, object);
+ }
+ }
+
+ function parsePatch(element, object) {
+ for (const [key, value] of Object.entries(object)) {
+ if (typeof value === 'object')
+ parsePatch(element[key], value);
+ else
+ element[key] = value;
+ }
+ }
+
+ async function fadeBlock() {
+ startPage.style.opacity = 0;
+ await wait(0.2);
+ startPage.style.display = 'none';
+ await wait(0.2);
+ templatePage.style.display = 'block';
+ templatePage.style.opacity = 1;
+ }
+
+ internal__submit.addEventListener('click', async () => await proceed())
+}
diff --git a/scripts/sdk.js b/scripts/sdk.js
new file mode 100644
index 0000000..e562754
--- /dev/null
+++ b/scripts/sdk.js
@@ -0,0 +1,30 @@
+async function fetchResource(id) {
+ const response = await fetch(ROUTES.GET_RESOURCE(id));
+ if (!response.ok) {
+ const json = await response.json();
+
+ throw new Error(`${json.error}: ${json.message}`);
+ }
+
+ return (await response.json()).resource;
+}
+
+async function fetchStatus() {
+ const response = await fetch(ROUTES.STATUS);
+
+ if (!response.ok) {
+ const json = await response.json();
+
+ throw new Error(`${json.error}: ${json.message}`);
+ }
+
+ const json = await response.json();
+ if (json.status !== 'ok') {
+ throw new Error('API status is not OK. Something went wrong.');
+ }
+
+ return json;
+}
+
+// on error, provide alert to the user
+window.addEventListener('unhandledrejection', event => alert(event.reason));
\ No newline at end of file
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..bb43c26
--- /dev/null
+++ b/style.css
@@ -0,0 +1,72 @@
+@import url('data/custom-style.css');
+@import url('https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,100..900;1,100..900&display=swap');
+
+:root {
+ --text: #fff;
+ --background: #2f3136;
+ --button-color: #a8c0ff;
+ --button-color-hover: #c4d5ff;
+}
+
+html, body {
+ width: 100%;
+ height: 100%;
+}
+
+body {
+ font-family: 'Raleway', sans-serif;
+ padding: 0;
+ margin: 0;
+}
+
+#internal__start-page, #internal__template {
+ transition: opacity 0.2s ease-in-out;
+}
+
+#internal__template {
+ display: none;
+}
+
+#internal__start-page {
+ width: 100%;
+ height: 100%;
+
+ gap: 5px;
+ display: grid;
+ place-items: center;
+ align-content: center;
+
+ background-color: var(--background) !important;
+}
+
+#internal__start-page > *:is(h1, h2, h3, h4, h5, h6, p, span) {
+ color: var(--text);
+}
+
+#internal__start-page > input {
+ border-radius: 10px;
+ text-align: center;
+ height: 50px;
+ border: none;
+}
+
+#internal__start-page > input:focus {
+ border: none;
+ outline: none;
+}
+
+#internal__start-page > button {
+ border: none;
+ outline: none;
+ cursor: pointer;
+
+ height: 50px;
+ min-width: 100px;
+ border-radius: 10px;
+ background-color: var(--button-color);
+ transition: background-color 0.2s ease-in-out;
+}
+
+#internal__start-page > button:hover {
+ background-color: var(--button-color-hover);
+}
\ No newline at end of file