├── .gitignore ├── ui-testing-recording.gif ├── readme-files ├── boeard.png ├── logo-main.png ├── GcMxoVk5Wl.gif ├── basic-todo.gif └── todo-bonus.gif ├── README.md ├── .vscode └── launch.json ├── .github └── workflows │ └── main.yaml ├── package.json ├── src ├── index.html ├── main.js └── style.css └── final.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /ui-testing-recording.gif: -------------------------------------------------------------------------------- 1 | GIF89a -------------------------------------------------------------------------------- /readme-files/boeard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/My-To-Do-List/HEAD/readme-files/boeard.png -------------------------------------------------------------------------------- /readme-files/logo-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/My-To-Do-List/HEAD/readme-files/logo-main.png -------------------------------------------------------------------------------- /readme-files/GcMxoVk5Wl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/My-To-Do-List/HEAD/readme-files/GcMxoVk5Wl.gif -------------------------------------------------------------------------------- /readme-files/basic-todo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/My-To-Do-List/HEAD/readme-files/basic-todo.gif -------------------------------------------------------------------------------- /readme-files/todo-bonus.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david35008/My-To-Do-List/HEAD/readme-files/todo-bonus.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Scale-Up Velocity](./readme-files/logo-main.png) Pre Course Project - Todo List 2 | 3 | ## My first javascript project. 4 | 5 | This project include Todo List Web Application, in which the user can store prioritized _todo tasks_ and view/sort that list 6 | 7 | the gif: 8 | ![alt text](./readme-files/GcMxoVk5Wl.gif) 9 | 10 | my video: https://drive.google.com/file/d/1W3vW2GJ8QRdwMOKSk1r_0mVKSWDSinqp/view 11 | 12 | the vercel app: https://my-to-do-try-1-aczpf3dop.vercel.app/ 13 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "pwa-chrome", 9 | "request": "launch", 10 | "name": "Launch Chrome against localhost", 11 | "url": "http://localhost:8080", 12 | "webRoot": "${workspaceFolder}" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | 2 | name: Node.js CI 3 | 4 | on: 5 | pull_request: 6 | branches: [ master ] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | matrix: 14 | node-version: [10.x] 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Run tests (Node.js ${{ matrix.node-version }}) 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | - run: npm ci 23 | - name: Run Client Tests 24 | run: USER_REPORT=${{ github.actor }} REPO_REPORT=${{ github.repository }} node_modules/.bin/jest -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "f4s-pre-course-exam", 3 | "version": "1.0.0", 4 | "description": "This exam will include most of the topics you learn during the last weeks, feel free go back to the mini tasks you had and use them for solving this exam. This repository include basic structure for todo list application you can change or modify it. [For more details](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div) The task is to create a todo application by following the requirements", 5 | "main": "main.js", 6 | "scripts": { 7 | "test": "node_modules/.bin/jest" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/GuySerfaty/f4s-pre-course-exam.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/GuySerfaty/f4s-pre-course-exam/issues" 17 | }, 18 | "homepage": "https://github.com/GuySerfaty/f4s-pre-course-exam#readme", 19 | "devDependencies": { 20 | "@suvelocity/tester": "^1.0.2", 21 | "jest": "^26.0.1", 22 | "puppeteer": "^4.0.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | To Do list 9 | 10 | 11 | 12 | 13 |

My Tasks list

14 |
15 | 16 | 17 | 18 | 26 | 27 |
28 | 29 |
30 | 31 |
Done  Pr  Createed Time       Task Description
32 | 33 | 34 |

0 Tasks On The List

35 | 36 |
37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /final.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment node 3 | */ 4 | const puppeteer = require('puppeteer'); 5 | const full4s = require('@suvelocity/tester'); 6 | 7 | const path = 'file://' + __dirname + '/src/index.html' 8 | let page; 9 | let browser; 10 | 11 | const secondTaskText = 'second task input'; 12 | jest.setTimeout(10000); 13 | const projectName = 'pre.Todo App'; 14 | describe(projectName, () => { 15 | beforeAll(async () => { 16 | browser = await puppeteer.launch() 17 | page = await browser.newPage() 18 | await page.goto(path, { waitUntil: 'networkidle0' }) 19 | await full4s.beforeAll(); 20 | }); 21 | afterEach(async () => { 22 | await full4s.afterEach(page); 23 | }) 24 | afterAll(async () => { 25 | await full4s.afterAll(projectName); 26 | await browser.close(); 27 | }); 28 | test('The todo list should be empty first', async () => { 29 | const elements = await page.$$('.todoText'); 30 | expect(elements.length).toBe(0); 31 | }); 32 | test('Can add todo task with text and priority', async () => { 33 | const firstTaskText = 'first task input'; 34 | const secondTaskText = 'second task input'; 35 | await page.type('#textInput', firstTaskText); 36 | await page.select('#prioritySelector', '1'); 37 | await page.click('#addButton'); 38 | const elements = await page.$$('.todoText'); 39 | const firstItem = await (await elements[0].getProperty('innerText')).jsonValue(); 40 | const priorityElements = await page.$$('.todoPriority'); 41 | const firstItemPriority = await (await priorityElements[0].getProperty('innerText')).jsonValue(); 42 | 43 | expect(elements.length).toBe(1) 44 | expect(firstItem).toBe(firstTaskText) 45 | expect(firstItemPriority).toBe('1') 46 | }); 47 | test('After add task the input should be empty', async () => { 48 | 49 | await page.type('#textInput', secondTaskText); 50 | await page.select('#prioritySelector', '4'); 51 | await page.click('#addButton'); 52 | const inputElement = await page.$('#textInput'); 53 | const currentInput = await (await inputElement.getProperty('value')).jsonValue(); 54 | expect(currentInput).toBe('') 55 | }); 56 | test('Task should be added in the end of the list', async () => { 57 | const elements = await page.$$('.todoText'); 58 | const secondItem = await (await elements[1].getProperty('innerText')).jsonValue(); 59 | 60 | const priorityElements = await page.$$('.todoPriority'); 61 | const secondItemPriority = await (await priorityElements[1].getProperty('innerText')).jsonValue(); 62 | expect(secondItem).toBe(secondTaskText) 63 | expect(secondItemPriority).toBe('4') 64 | }); 65 | test('Counter increase', async () => { 66 | const counterElement = await page.$('#counter'); 67 | const currentCounter = await (await counterElement.getProperty('innerText')).jsonValue(); 68 | expect(currentCounter).toBe("2") 69 | }); 70 | test('Bonus - Can sort by priority', async () => { 71 | await page.click('#sortButton'); 72 | const elements = await page.$$('.todoText'); 73 | const secondItem = await (await elements[0].getProperty('innerText')).jsonValue(); 74 | const priorityElements = await page.$$('.todoPriority'); 75 | const secondItemPriority = await (await priorityElements[0].getProperty('innerText')).jsonValue(); 76 | expect(secondItem).toBe(secondTaskText) 77 | expect(secondItemPriority).toBe('4') 78 | }); 79 | }) 80 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | const tasksList = document.getElementById('tasksList'); 2 | let toDoList = []; 3 | 4 | // Load the document changes from the local storage 5 | function getItemStored() { 6 | if (JSON.parse(localStorage.getItem("mytasks"))[0] !== undefined) { 7 | let reply = confirm("All your current tasks will be deleted.\nare you sure?"); 8 | if (reply == true) { 9 | toDoList = JSON.parse(localStorage.getItem("mytasks")); 10 | refreshDisplay(); 11 | }; 12 | }; 13 | }; 14 | // add button to load changes 15 | const loadChanges = document.getElementById('loadChanges'); 16 | loadChanges.addEventListener('click', getItemStored); 17 | 18 | // Add zero padding to 2 digits numbers 19 | function pad(num) { return ('00' + num).slice(-2) }; 20 | 21 | // Change the date to SQL date format 22 | function formatDate(date) { 23 | dateStr = date.getUTCFullYear() + '-' + 24 | pad(date.getUTCMonth() + 1) + '-' + 25 | pad(date.getUTCDate()) + ' ' + 26 | pad(date.getUTCHours() + 3) + ':' + 27 | pad(date.getUTCMinutes()) + ':' + 28 | pad(date.getUTCSeconds()); 29 | return dateStr; 30 | }; 31 | 32 | // Add task to the "do_List" array and add it to the dispaly 33 | function addTask() { 34 | const taskDescription = textInput.value; 35 | const priority_Num = priorityChoose.value; 36 | if (taskDescription !== "" && priority_Num !== "--") { 37 | let obj = { 38 | priority_Num: priority_Num, 39 | date: formatDate(new Date()), 40 | taskDescription: taskDescription, 41 | checked: false 42 | }; 43 | toDoList.push(obj); 44 | refreshDisplay(); 45 | addDraggingOption(); 46 | textInput.value = ""; 47 | priorityChoose.value = "--"; 48 | }; 49 | }; 50 | // Add input elements for adding tasks 51 | const textInput = document.getElementById('textInput'); 52 | const priorityChoose = document.getElementById('prioritySelector'); 53 | const addButton = document.getElementById('addButton'); 54 | addButton.addEventListener('click', addTask); 55 | 56 | 57 | // add funtion for node self-removal 58 | Element.prototype.remove = function () { 59 | this.parentElement.removeChild(this); 60 | }; 61 | 62 | 63 | // Apply the line-through decoration for done tasks 64 | function applyStyle(checkbox, containerDiv) { 65 | if (checkbox.checked == true) { 66 | containerDiv.style = "text-decoration: line-through;"; 67 | } 68 | else { 69 | containerDiv.style = ""; 70 | }; 71 | }; 72 | 73 | // Add line to display 74 | function addTaskLine(taskObj) { 75 | const containerDiv = addChild(tasksList, "todoContainer"); 76 | addChild(containerDiv, "todoPriority", taskObj.priority_Num); 77 | addChild(containerDiv, "todoCreatedAt", taskObj.date); 78 | addChild(containerDiv, "todoText", taskObj.taskDescription); 79 | 80 | const checkBox = document.createElement('input'); 81 | checkBox.type = "checkbox"; 82 | checkBox.className = "checkbox"; 83 | checkBox.checked = taskObj.checked; 84 | containerDiv.appendChild(checkBox); 85 | applyStyle(checkBox, containerDiv); 86 | 87 | checkBox.onclick = () => { 88 | applyStyle(checkBox, containerDiv); 89 | copyToArray(); 90 | }; 91 | const delButton = document.createElement('button'); 92 | delButton.className = "delButton"; 93 | containerDiv.appendChild(delButton); 94 | delButton.onclick = () => { 95 | delButton.parentElement.remove(); 96 | copyToArray(); 97 | refreshDisplay(); 98 | }; 99 | }; 100 | 101 | 102 | // Add div child 103 | function addChild(parent, className, taskDescription) { 104 | let newDiv = document.createElement('div'); 105 | parent.appendChild(newDiv); 106 | newDiv.className = className; 107 | newDiv.textContent = taskDescription; 108 | return newDiv; 109 | }; 110 | 111 | // Add event to the "sort" button 112 | const orderButton = document.getElementById('sortButton'); 113 | orderButton.addEventListener('click', orderList); 114 | 115 | // Order the tasks list by priority 116 | function orderList() { 117 | toDoList.sort(function (a, b) { 118 | return +b.priority_Num - +a.priority_Num; 119 | }); 120 | refreshDisplay(); 121 | }; 122 | 123 | // Remove all child nodes of a list 124 | function removeAllChildren(parent) { 125 | while (parent.hasChildNodes()) { 126 | parent.removeChild(parent.firstChild); 127 | }; 128 | }; 129 | 130 | // Clean the list on display and add the new list to display 131 | function refreshDisplay() { 132 | removeAllChildren(tasksList); 133 | 134 | for (let i = 0; i < toDoList.length; i++) { 135 | addTaskLine(toDoList[i]); 136 | }; 137 | document.getElementById('counter').textContent = toDoList.length; 138 | addDraggingOption(); 139 | }; 140 | 141 | // Copy the display to the toDoList array: 142 | function copyToArray() { 143 | toDoList.splice(0, toDoList.length); 144 | listItemsElements = document.getElementById('tasksList').children; 145 | for (let i = 0; i < listItemsElements.length; i++) { 146 | const element = listItemsElements[i]; 147 | 148 | let obj = { 149 | priority_Num: element.children[0].textContent, 150 | date: element.children[1].textContent, 151 | taskDescription: element.children[2].textContent, 152 | checked: element.children[3].checked 153 | }; 154 | toDoList.push(obj); 155 | }; 156 | }; 157 | 158 | // Add option "Clear All" button 159 | function cleartasks() { 160 | var reply = confirm("are you sure?"); 161 | if (reply == true) { 162 | toDoList.splice(0, toDoList.length); 163 | refreshDisplay(); 164 | }; 165 | }; 166 | const clearbutton = document.getElementById('clearButton'); 167 | clearbutton.addEventListener('click', cleartasks); 168 | 169 | // Add option to save the tasks to the local storage 170 | function svaechanges() { 171 | if (toDoList[0] == undefined) { 172 | localStorage.clear(); 173 | } else { 174 | localStorage.setItem("mytasks", JSON.stringify(toDoList)); 175 | }; 176 | }; 177 | const svaeChanges = document.getElementById('svaeChanges'); 178 | svaeChanges.addEventListener('click', svaechanges); 179 | 180 | // Drag and drop element in a list 181 | function addDraggingOption() { 182 | const list = document.getElementById('tasksList'); 183 | let draggingEle; 184 | let placeholder; 185 | let isDraggingStarted = false; 186 | 187 | // Swap two nodes 188 | const swap = function (nodeA, nodeB) { 189 | const parentA = nodeA.parentNode; 190 | const siblingA = nodeA.nextSibling === nodeB ? nodeA : nodeA.nextSibling; 191 | nodeB.parentNode.insertBefore(nodeA, nodeB); 192 | parentA.insertBefore(nodeB, siblingA); 193 | }; 194 | 195 | // Check if "nodeA" is above "nodeB" 196 | const isAbove = function (nodeA, nodeB) { 197 | 198 | // Get the bounding rectangle of nodes 199 | const rectA = nodeA.getBoundingClientRect(); 200 | const rectB = nodeB.getBoundingClientRect(); 201 | return (rectA.top + rectA.height / 2 < rectB.top + rectB.height / 2); 202 | }; 203 | 204 | // The current position of mouse relative to the dragging element 205 | let x = 0; 206 | let y = 0; 207 | 208 | // Handling the mouse click down event 209 | const mouseDownHandler = function (e) { 210 | if (e.target.className == "delButton" || e.target.className == "checkbox") { 211 | return; 212 | }; 213 | draggingEle = e.currentTarget; 214 | 215 | // Calculate the mouse position 216 | const rect = draggingEle.getBoundingClientRect(); 217 | x = e.pageX - rect.left; 218 | y = e.pageY - rect.top; 219 | 220 | // Attach the listeners to "document" 221 | document.addEventListener('mousemove', mouseMoveHandler); 222 | document.addEventListener('mouseup', mouseUpHandler); 223 | }; 224 | 225 | // Handling the mouse move event 226 | const mouseMoveHandler = function (e) { 227 | const draggingRect = draggingEle.getBoundingClientRect(); 228 | if (!isDraggingStarted) { 229 | isDraggingStarted = true; 230 | 231 | // For the next element won't move up 232 | placeholder = document.createElement('div'); 233 | placeholder.classList.add('placeholder'); 234 | draggingEle.parentNode.insertBefore(placeholder, draggingEle.nextSibling); 235 | placeholder.style.height = `${draggingRect.height}px`; 236 | }; 237 | 238 | // Set position for dragging element 239 | draggingEle.style.position = 'absolute'; 240 | draggingEle.style.top = `${e.pageY - y}px`; 241 | draggingEle.style.left = `${e.pageX - x}px`; 242 | const prevEle = draggingEle.previousElementSibling; 243 | const nextEle = placeholder.nextElementSibling; 244 | 245 | // Move the dragg element to the top 246 | if (prevEle && isAbove(draggingEle, prevEle)) { 247 | swap(placeholder, draggingEle); 248 | swap(placeholder, prevEle); 249 | return; 250 | }; 251 | 252 | // move the dragg element to the bottom 253 | if (nextEle && isAbove(nextEle, draggingEle)) { 254 | swap(nextEle, placeholder); 255 | swap(nextEle, draggingEle); 256 | }; 257 | }; 258 | // Handling the mouse click up event 259 | const mouseUpHandler = function () { 260 | placeholder && placeholder.parentNode.removeChild(placeholder); 261 | draggingEle.style.removeProperty('top'); 262 | draggingEle.style.removeProperty('left'); 263 | draggingEle.style.removeProperty('position'); 264 | x = null; 265 | y = null; 266 | draggingEle = null; 267 | isDraggingStarted = false; 268 | document.removeEventListener('mousemove', mouseMoveHandler); 269 | document.removeEventListener('mouseup', mouseUpHandler); 270 | copyToArray(); 271 | refreshDisplay(); 272 | }; 273 | 274 | // Query all items 275 | [].slice.call(list.getElementsByClassName('todoContainer')).forEach(function (item) { 276 | item.addEventListener('mousedown', mouseDownHandler); 277 | }); 278 | }; -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | text-align:center; 3 | height: 100%; 4 | width: 100%; 5 | background-image: url("https://thumbs.dreamstime.com/b/blue-sky-clouds-abstract-art-background-watercolor-digital-artwork-136551201.jpg"); 6 | background-size: 100%; 7 | background-repeat: no-repeat; 8 | } 9 | 10 | #control-section { 11 | display: inline-flex; 12 | clear: both; 13 | width: auto; 14 | } 15 | 16 | #view-section { 17 | width: 480px; 18 | min-height: 700px; 19 | margin: auto; 20 | background-image: url("/readme-files/boeard.png"); 21 | background-repeat: no-repeat; 22 | } 23 | 24 | h1 { 25 | margin: 0px 0px 0px 0px; 26 | padding: 0px 0px 0px 0px; 27 | color: seashell; 28 | font-size: 100px; 29 | } 30 | 31 | #tasksList { 32 | width: 85%; 33 | } 34 | 35 | .todoContainer { 36 | margin: 0px 0px 0px 0px; 37 | padding: 0px 0px 0px 0px; 38 | position: relative; 39 | clear: both; 40 | border-bottom: 1px solid black; 41 | display: flex; 42 | cursor: move; 43 | user-select: none; 44 | } 45 | 46 | .todoContainer:hover { 47 | border: 2px solid rgb(58, 39, 235); 48 | } 49 | 50 | .todoContainer div { 51 | flex: 1; 52 | } 53 | 54 | .todoPriority { 55 | margin: 1px 0px 0px 0px; 56 | padding: 0px 0px 0px 25px; 57 | position: absolute; 58 | font-size: 17px; 59 | } 60 | 61 | .todoCreatedAt { 62 | font-family: Arial, Helvetica, sans-serif; 63 | font-size: 11px; 64 | font-style: bold; 65 | margin: 2px 0px 0px 0px; 66 | padding: 4px 0px 0px 45px; 67 | position: absolute; 68 | float: left; 69 | } 70 | 71 | .todoText { 72 | font-family: Arial, Helvetica, sans-serif; 73 | font-size: 13px; 74 | text-align: left; 75 | margin: 5px 35px 0px 0px; 76 | padding: 0px 0px 0px 155px; 77 | } 78 | 79 | #clearButton { 80 | margin: 0px 0px 0px 0px; 81 | padding: 0px 0px 0px 0px; 82 | cursor:pointer; 83 | } 84 | 85 | #svaeChanges { 86 | margin: 0px 0px 0px 0px; 87 | background-image: url("https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcTWO7C3qXLgYX7RfqNIaygSPDnv7vwwsozVSg&usqp=CAU"); 88 | background-size: 100%; 89 | background-repeat: no-repeat; 90 | border-radius: 50%; 91 | cursor:pointer; 92 | width: 40px; 93 | height: 40px; 94 | left: 16px; 95 | } 96 | 97 | #loadChanges { 98 | margin: 0px 0px 0px 0px; 99 | background-image: url("https://cdn3.iconfinder.com/data/icons/web-icons-1/64/Cloud_Download-512.png"); 100 | background-size: 90%; 101 | background-repeat: no-repeat; 102 | border-radius: 50%; 103 | cursor:pointer; 104 | width: 40px; 105 | height: 40px; 106 | } 107 | 108 | #textInput{ 109 | margin: 8px 0px 0px 0px; 110 | width: 200px; 111 | height: 20px; 112 | clear: both; 113 | font-size: 15px; 114 | } 115 | 116 | #prioritySelector { 117 | margin: 8px 0px 0px 0px; 118 | cursor:pointer; 119 | font-weight: bold; 120 | width: 40px; 121 | height: 25px; 122 | } 123 | 124 | #addButton { 125 | margin: 0px 0px 0px 0px; 126 | background-image: url("data:image/jpeg;base64,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"); 127 | background-size: 100%; 128 | background-repeat: no-repeat; 129 | border-radius: 50%; 130 | cursor:pointer; 131 | width: 40px; 132 | height: 40px; 133 | } 134 | 135 | #sortButton { 136 | float: right; 137 | margin: 65px 42px 0px 30px; 138 | cursor:pointer; 139 | background-color: DodgerBlue; 140 | color: white; 141 | padding: 5px 16px; 142 | } 143 | 144 | #sortButton:hover { 145 | background-color: saddlebrown; 146 | } 147 | 148 | .delButton { 149 | position: absolute; 150 | margin: 0px 0px 0px 390px; 151 | background-image: url("https://e7.pngegg.com/pngimages/616/37/png-clipart-trash-can-illustration-computer-icons-icon-design-delete-button-miscellaneous-text.png"); 152 | background-size: 100%; 153 | border: none; 154 | background-repeat: no-repeat; 155 | cursor:pointer; 156 | width: 13px; 157 | height: 17px; 158 | } 159 | 160 | .delButton:hover { 161 | background-color: maroon; 162 | } 163 | 164 | #clearButton { 165 | margin-top: 100px; 166 | background-color: saddlebrown; 167 | } 168 | 169 | #clearButton:hover { 170 | background-color: maroon; 171 | } 172 | 173 | .checkbox { 174 | position: absolute; 175 | margin: 5px 0px 0px 0px; 176 | padding: 0px 0px 0px 0px; 177 | cursor:pointer; 178 | } 179 | 180 | pre { 181 | border-bottom: solid 0.5px ; 182 | font-size: 10px; 183 | float: left; 184 | margin: 90px 0px 0px 31px; 185 | } 186 | p { 187 | margin: 0px 0px 0px 0px; 188 | padding: 0px 0px 0px 0px; 189 | color: black; 190 | font-size: 30px; 191 | font-style: italic; 192 | } 193 | 194 | #counter { 195 | font-size: 50px; 196 | } 197 | 198 | .placeholder { 199 | border: 2px dashed #cbd5e0; 200 | margin-bottom: 1rem; 201 | } --------------------------------------------------------------------------------