├── .github └── workflows │ └── frontend.yml ├── .gitignore ├── README.md └── frontend ├── favicon-32x32.png ├── index.html ├── main.js └── style.css /.github/workflows/frontend.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to Google Cloud Storage 2 | 3 | on: 4 | push: 5 | 6 | jobs: 7 | deploy: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | 12 | - name: Set up Cloud SDK 13 | uses: google-github-actions/setup-gcloud@v0.2.0 14 | with: 15 | service_account_key: ${{ secrets.GCP_SA_KEY }} 16 | project_id: ${{ secrets.GCP_PROJECT_ID }} 17 | 18 | - name: Deploy to Google Cloud Storage 19 | run: | 20 | gsutil cp ./frontend/* gs://cvbucket/ 21 | 22 | - name: Invalidate Cloud CDN Cache 23 | run: | 24 | gcloud compute url-maps invalidate-cdn-cache cvcloud-lb --path "/*" --async 25 | env: 26 | CLOUDSDK_CORE_PROJECT: ${{ secrets.GCP_PROJECT_ID }} 27 | GOOGLE_APPLICATION_CREDENTIALS: /tmp/gcloud-sa.json 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .nox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | *.py,cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | db.sqlite3 60 | db.sqlite3-journal 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | doc/build/ 72 | doc/source/gen/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # pyenv 81 | .python-version 82 | 83 | # pipenv 84 | #Pipfile.lock 85 | 86 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 87 | __pypackages__/ 88 | 89 | # Celery stuff 90 | celerybeat-schedule 91 | celerybeat.pid 92 | 93 | # SageMath parsed files 94 | *.sage.py 95 | 96 | # Environments 97 | .env 98 | .venv 99 | env/ 100 | venv/ 101 | ENV/ 102 | env.bak/ 103 | venv.bak/ 104 | 105 | # Spyder project settings 106 | .spyderproject 107 | .spyproject 108 | 109 | # Rope project settings 110 | .ropeproject 111 | 112 | # mkdocs documentation 113 | /site 114 | 115 | # mypy 116 | .mypy_cache/ 117 | .dmypy.json 118 | dmypy.json 119 | 120 | # Pyre type checker 121 | .pyre/ 122 | 123 | # pytype static type analyzer 124 | .pytype/ 125 | 126 | # Cython debug symbols 127 | cython_debug/ 128 | 129 | # Terraform 130 | .terraform/ 131 | *.tfstate 132 | *.tfstate.* 133 | .terraform.lock.hcl 134 | 135 | # VSCode 136 | .vscode/ 137 | 138 | # Test reports 139 | reports/ 140 | 141 | # Frontend: Node 142 | node_modules/ 143 | npm-debug.log 144 | yarn-error.log 145 | yarn-debug.log 146 | 147 | # Frontend: Bower 148 | bower_components/ 149 | 150 | # Frontend: Misc 151 | .DS_Store 152 | Thumbs.db 153 | *.log 154 | *.csv 155 | *.sublime-project 156 | *.sublime-workspace 157 | .idea/ 158 | *.swp 159 | *.swo 160 | *.sass-cache 161 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Frontend for GCP Resume Challenge - [cloudcv.se](https://cloudcv.se/) 2 | 3 | This repository is part of the GCP Resume Challenge, showcasing my serverless, dynamic resume hosted on Google Cloud Platform. It details the frontend aspect of the project, including HTML, CSS, JavaScript, and CI/CD pipeline configuration. 4 | 5 | ## What I Did 6 | 7 | - **Developed a Static Web Resume**: Created `index.html` and `style.css` for the website's structure and style. 8 | - **Dynamic Visitor Counter**: Implemented with `main.js` to display real-time visitor count. 9 | - **CI/CD Integration**: Configured continuous integration and deployment using `frontend.yml`. 10 | 11 | ## How I Did It 12 | 13 | ### HTML & CSS 14 | 15 | - Structured the resume content using HTML. 16 | - Applied modern CSS styling for a responsive layout. 17 | 18 | ### JavaScript 19 | 20 | - Implemented the visitor counter logic in `main.js`, interacting with the backend for live data. 21 | 22 | ### CI/CD with Cloud Build 23 | 24 | - Used `frontend.yml` to set up a CI/CD pipeline. 25 | - Automated the process of testing, building, and deploying the website to Google Cloud Storage. 26 | 27 | ## Why I Did What I Did 28 | 29 | ### Technology Choices 30 | 31 | - Chose HTML/CSS/JavaScript for their simplicity and effectiveness in creating static web pages. 32 | - Selected serverless architecture for backend interaction to demonstrate cloud skills and ensure scalability. 33 | 34 | ### Implementation Strategy 35 | 36 | - Aimed for a minimalist and efficient design for enhanced performance and user experience. 37 | - Integrated real-time data to showcase dynamic web development capabilities. 38 | 39 | ## Conclusion 40 | 41 | This project highlights my skills in web development and understanding of cloud technologies, particularly within the GCP ecosystem. The integration of CI/CD practices demonstrates my commitment to modern development methodologies. 42 | 43 | Visit [cloudcv.se](https://cloudcv.se/) to see the live resume and the visitor counter in action! 44 | -------------------------------------------------------------------------------- /frontend/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvulcu/frontend_CVcloud/152b9856515e677c138a49b8f7034489465d6951/frontend/favicon-32x32.png -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Maria Vulcu 6 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | mvulcu@CloudServer:~$ cd /mvulcu/about_me
25 | mvulcu@CloudServer:/mvulcu/about_me$ cat 26 | Maria_Vulcu.txt

27 | "Hey there, nice to see you here!"

28 | Welcome!

29 | My name is Maria Vulcu, I am a student in the cloud technology course, my resume can be viewed here: 30 | CV

31 | I typically dedicate my time to fascinating projects and I 32 | have a strong passion for exploring and acquiring new skills.

33 | Here is my GitHub link.
34 | You can visit my LinkedIn, here: LinkedIn

35 | You can also contact me through email, feel free to send me an email at 36 | "mashavulcu@yahoo.com" or lookmycv.se

38 | Have an amazing day!_

39 | mvulcu@CloudServer:/mvulcu/about_me$ exit

40 | mvulcu@CloudServer:~$ visitors: 41 |
Loading...

42 | 43 |
44 |
45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /frontend/main.js: -------------------------------------------------------------------------------- 1 | async function updatemyVisitorCount() { 2 | try { 3 | 4 | const response = await fetch('https://us-central1-crafty-campaign-401215.cloudfunctions.net/current_number_visitors'); 5 | 6 | if (response.ok) { 7 | 8 | const data = await response.json(); 9 | 10 | 11 | document.getElementById('visitor-counter').textContent = data.new_number; 12 | } else { 13 | console.error('Failed to fetch visitor count:', response.statusText); 14 | } 15 | } catch (error) { 16 | console.error('Error fetching visitor count:', error.message); 17 | } 18 | } 19 | 20 | window.onload = updatemyVisitorCount; -------------------------------------------------------------------------------- /frontend/style.css: -------------------------------------------------------------------------------- 1 | /* Default styles */ 2 | body { 3 | background-color: #272727; 4 | padding: 10px; 5 | font-family: Arial, sans-serif; 6 | } 7 | 8 | .fakeButtons { 9 | height: 10px; 10 | width: 10px; 11 | border-radius: 50%; 12 | border: 1px solid #000; 13 | position: relative; 14 | top: 6px; 15 | left: 6px; 16 | background-color: #ff3b47; 17 | border-color: #9d252b; 18 | display: inline-block; 19 | } 20 | 21 | .fakeMinimize { 22 | left: 11px; 23 | background-color: #ffc100; 24 | border-color: #9d802c; 25 | } 26 | 27 | .fakeZoom { 28 | left: 16px; 29 | background-color: #00d742; 30 | border-color: #049931; 31 | } 32 | 33 | .fakeMenu { 34 | max-width: 100%; 35 | height: 25px; 36 | background-color: #bbb; 37 | margin: 0 auto; 38 | border-top-right-radius: 5px; 39 | border-top-left-radius: 5px; 40 | } 41 | 42 | .fakeScreen { 43 | background-color: #151515; 44 | box-sizing: border-box; 45 | width: 100%; 46 | margin: 0 auto; 47 | padding: 20px; 48 | border-bottom-left-radius: 5px; 49 | border-bottom-right-radius: 5px; 50 | } 51 | 52 | p { 53 | text-align: left; 54 | font-size: 1.25em; 55 | white-space: normal; 56 | overflow: hidden; 57 | color: rgb(197, 82, 47); 58 | text-decoration: none; 59 | } 60 | 61 | a { 62 | color: rgb(197, 185, 47); 63 | text-decoration: none; 64 | font-weight: bold; 65 | } 66 | 67 | #a { 68 | color: #0f0; 69 | } 70 | 71 | #c { 72 | color: rgb(241, 246, 247); 73 | } 74 | 75 | #b { 76 | color: #632ff1; 77 | } 78 | 79 | #k { 80 | animation: change 1s; 81 | color: #0f0; 82 | } 83 | 84 | /* Media queries for responsiveness */ 85 | @media (max-width: 768px) { 86 | .fakeMenu, 87 | .fakeScreen { 88 | width: 90%; 89 | } 90 | 91 | p { 92 | font-size: 1em; 93 | } 94 | } 95 | 96 | @media (max-width: 480px) { 97 | .fakeMenu, 98 | .fakeScreen { 99 | width: 100%; 100 | } 101 | 102 | p { 103 | font-size: 0.9em; 104 | } 105 | } 106 | 107 | span { 108 | color: #fff; 109 | font-weight: bold; 110 | } 111 | 112 | .line1 { 113 | color: #9CD9F0; 114 | animation: type .5s 1s steps(20, end) forwards; 115 | } 116 | 117 | .cursor1 { 118 | animation: blink 1s 2s 2 forwards; 119 | } 120 | 121 | .line2 { 122 | color: #CDEE69; 123 | animation: type .5s 4.25s steps(20, end) forwards; 124 | } 125 | 126 | .cursor2 { 127 | animation: blink 1s 5.25s 2 forwards; 128 | } 129 | 130 | .line3 { 131 | color: #E09690; 132 | animation: type .5s 7.5s steps(20, end) forwards; 133 | } 134 | 135 | .cursor3 { 136 | animation: blink 1s 8.5s 2 forwards; 137 | } 138 | 139 | .line4 { 140 | color: #fff; 141 | animation: type .5s 10.75s steps(20, end) forwards; 142 | } 143 | 144 | .cursor4 { 145 | animation: blink 1s 11.5s infinite; 146 | } 147 | 148 | #visitor-counter { 149 | display: inline; 150 | animation: blink 1s infinite; 151 | color: rgb(128, 0, 32); 152 | font-weight: bold; 153 | } 154 | 155 | @keyframes blink { 156 | 0%, 40%, 100% { 157 | opacity: 0; 158 | } 159 | 160 | 50%, 90% { 161 | opacity: 1; 162 | } 163 | } 164 | 165 | @keyframes type { 166 | to { 167 | width: 17em; 168 | } 169 | } 170 | 171 | @keyframes change { 172 | 0% { 173 | color: #0f0; 174 | } 175 | 176 | 50% { 177 | color: #0f0; 178 | } 179 | 180 | 99% { 181 | color: black; 182 | } 183 | } 184 | 185 | #console { 186 | font-family: courier, monospace; 187 | color: #fff; 188 | max-width: 750px; 189 | margin-left: auto; 190 | margin-right: auto; 191 | margin-top: 10px; 192 | font-size: 14px; 193 | word-wrap: break-word; 194 | } 195 | 196 | #info { 197 | font-family: courier, monospace; 198 | color: #fff; 199 | max-width: 750px; 200 | margin-left: auto; 201 | margin-right: auto; 202 | margin-top: 100px; 203 | font-size: 14px; 204 | } 205 | --------------------------------------------------------------------------------