User Profile page with firebase - the dossier
I decided to give my users a dedicated page to edit their profile, to make things more appealing and have more information to personalize their experience down the line. The result is the dossier page, where signed in users can edit their personal information, such as name, gender, code name, password and other things. I gave it a theme of a secret agent’s profile, mixed with esoteric symbolism and fantasy game appeal, to fit the theme of the website. Users can write their own cover identity and will advance in rank every month automatically. Every rank is not just a number but has an assigned name. Also users can write out their expertise. Apart from that the user’s e-mail is displayed as handle and there is a briefing section, these two, along with the rank are not editable by the user. The command brief used to be edited by me manually in the firebase console, a little something I thought would be fun to do randomly, to encourage users and make things more personal. However, as of the last update it is automated and changes roughly every half a month. Also users can now select an avatar, with a selection per gender. The HTML is the whole dossier, I didn’t use squarespace for this, apart from embedding the result on the page, in place of a text block.
HTML: <div id="dossier-MainContainer"> <div id="dossier-Container"> <div id="dossier-profileImageContainer"> <div id="dossier-profileImageInnerContainer"> <div id="dossier-profileImage-left" class="dossier-profileImage-control left"></div> <div id="dossier-profileImage" class="dossier-profileImage"></div> <div id="dossier-profileImage-right" class="dossier-profileImage-control right"></div> </div> </div> <div id="dossier-handleContainer" class="dossier-dataContainer"> <div id="dossier-handleDetail" class="dossier-detail">Handle:</div> <input type="text" id="dossier-handle-input" class="dossier-input" placeholder="classified" readonly/> </div> <div id="dossier-rankContainer" class="dossier-dataContainer"> <div id="dossier-rankDetail" class="dossier-detail">Rank:</div> <input type="text" id="dossier-rank-input" class="dossier-input" placeholder="classified" readonly/> </div> <div id="dossier-codenameContainer" class="dossier-dataContainer"> <div id="dossier-codenameDetail" class="dossier-detail">Code Name:</div> <input type="text" id="dossier-codename-input" class="dossier-input" maxlength="18" placeholder="classified" /> </div> <div id="dossier-realnameContainer" class="dossier-dataContainer"> <div id="dossier-realnameDetail" class="dossier-detail">Real Name:</div> <input type="text" id="dossier-realname-input" class="dossier-input" maxlength="50" placeholder="classified" /> </div> <div id="dossier-passwordContainer" class="dossier-dataContainer"> <div id="dossier-passwordDetail" class="dossier-detail">Password:</div> <input type="text" id="dossier-password-input" class="dossier-input" maxlength="18" placeholder="classified" /> </div> <div id="dossier-genderContainer" class="dossier-dataContainer"> <div id="dossier-genderDetail" class="dossier-detail">Gender:</div> <div class="dossier-OptionContainer"> <div id="dossier-male" class="dossier-RadioButton male"></div> <div class="dossier-radioDetail">Male</div> </div> <div class="dossier-OptionContainer"> <div id="dossier-female" class="dossier-RadioButton female"></div> <div class="dossier-radioDetail">Female</div> </div> <div class="dossier-OptionContainer"> <div id="dossier-noGender" class="dossier-RadioButton classified"></div> <div class="dossier-radioDetail">Classified</div> </div> </div> <div id="dossier-coverContainer" class="dossier-dataContainer"> <div id="dossier-coverDetail" class="dossier-detail">Cover Identity:</div> <textarea id="dossier-cover-text" class="dossier-input dossier-text" maxlength="500" placeholder="classified">Classified</textarea> </div> <div id="dossier-expertiseContainer" class="dossier-dataContainer"> <div id="dossier-expertiseDetail" class="dossier-detail">Expertise:</div> <textarea id="dossier-expertise-text" class="dossier-input dossier-text" maxlength="500" placeholder="classified">Classified</textarea> </div> <div id="dossier-notesContainer" class="dossier-dataContainer"> <div id="dossier-notesDetail" class="dossier-detail">Command Brief:</div> <textarea id="dossier-notes-text" class="dossier-input dossier-text" placeholder="classified" readonly>Classified</textarea> </div> <div id="dossier-buttonContainer" class="dossier-buttonContainer"> <button id="dossier-assignButton" class="sqs-block-button-element--medium sqs-button-element--primary sqs-block-button-element sqs-block-button-container">Update</button> </div> <div id="dossier-recruitedContainer" class="dossier-dataContainer"> <div id="dossier-recruitedText" class="dossier-simpleText">Recruited: Classified</div> <div id="dossier-evaluationText" class="dossier-simpleText">Rank Evaluation</div> <div id="dossier-briefingText" class="dossier-simpleText">Next Briefing</div> </div> </div> </div>
JAVASCRIPT: <script type="module"> // Initialize Firebase import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.12.5/firebase-app.js'; import { getAuth, signOut, onAuthStateChanged, updatePassword, reauthenticateWithCredential, EmailAuthProvider } from 'https://www.gstatic.com/firebasejs/10.12.5/firebase-auth.js'; import { getFirestore, collection, setDoc, writeBatch, deleteDoc, getDocs, query, serverTimestamp, where, doc, getDoc } from 'https://www.gstatic.com/firebasejs/10.12.5/firebase-firestore.js'; const app = initializeApp(firebaseConfig); const auth = getAuth(app); const db = getFirestore(app); //main stuff document.addEventListener('DOMContentLoaded', function() { const dossierMainContainer = document.getElementById('dossier-MainContainer'); const subString = '/accountdossier'; const getURL = window.location.href; if (getURL.includes(subString)) { const dossierEmbedBlock = document.getElementById('block-yui_3_17_2_1_1726225293920_42083'); const dossierEmbedBlockText = dossierEmbedBlock.querySelector('p'); const profileImage = document.getElementById('dossier-profileImage'); const profileImageInnerContainer = document.getElementById('dossier-profileImageInnerContainer'); const profileImageControlLeft = document.getElementById('dossier-profileImage-left'); const profileImageControlRight = document.getElementById('dossier-profileImage-right'); dossierEmbedBlockText.textContent = ''; dossierEmbedBlock.appendChild(dossierMainContainer); //initials let isMaster = false; let handle = ''; let codename = ''; let realName = ''; let gender = 'classified'; let memberSince = ''; let lastCheckIn = ''; let rank = ''; let rankNumber = 1; let rankTimeRemaining = 30; let oldRank = ''; let cover = ''; let expertise = ''; let notes = ''; let profilePic = ''; let profileImagesSelection; let currentImageIndex = 0; let accessGranted = false; let passwordUpdated = false; let codenameUpdated = false; let realNameUpdated = false; let genderUpdated = false; let coverUpdated = false; let expertiseUpdated = false; let codenameUpdateErrorMsg = 'Error updaing codename'; let updateErrorMsg = 'Error updaing dossier'; let passwordUpdateErrorMsg = 'Error updating password'; const forbiddenCodenames = ['master', 'user', 'admin', 'superuser', 'root', 'administrator', 'lev polivanov', 'manager', 'overseer', 'president', 'secret', 'stranger', 'unknown', 'mystery', 'archmage', 'supervisor', 'director', 'handler']; //ranks have been redacted, there are 33 of them const rankMap = new Map([ [1, 'Analyst'], ... [33, 'Arrowhead'] ]); //notes also redacted, there are 66 of them const notesMap = new Map([ [1, 'Welcome recruit! Your purpose is your own in these lower ranks, I trust that for now you will get accustomed to the company and the routine tasks. I like my coffee black, with milk, a modicum of sugar, no milk, only latte, sugar free, but with a pinch of caramel. Should you have questions, feel free to chat with other agents.'], ... [66, 'You do not miss, you do not waver. As arrowhead you have advanced to the league of agents which are not technically alive, nor really dead. You will exist inbetween, but your job will remain unchanged. You know what to do, keep it up. This is the final rank for now, agent. I trust your dormant status will elude us.'] ]); //images redacted, this is just an example const maleProfileImages = [ 'https://images.squarespace-cdn.com/content/v1/6654b2fee26f292de13f1a9d/1a11687d-4bb8-4c20-a5e7-4f02a939d5a8/AccountIcon_StandardMale1.png', 'https://images.squarespace-cdn.com/content/v1/6654b2fee26f292de13f1a9d/8b5fbd79-8f8c-4d41-b888-ebeebdcc6d3c/AccountIcon_Male2.png', 'https://images.squarespace-cdn.com/content/v1/6654b2fee26f292de13f1a9d/2f2a1ef6-0568-4706-acb4-e5f4195d9412/AccountIcon_Male3.png' ]; const femaleProfileImages = [ 'https://images.squarespace-cdn.com/content/v1/6654b2fee26f292de13f1a9d/593c8052-a087-4ac4-9213-6759f7f7be7c/AccountIcon_StandardFemale1.png', 'https://images.squarespace-cdn.com/content/v1/6654b2fee26f292de13f1a9d/72b4be0c-6c20-4317-a5fa-a9152abbafb7/AccountIcon_Female2.png', 'https://images.squarespace-cdn.com/content/v1/6654b2fee26f292de13f1a9d/8673c621-f9df-4333-baea-23c1985fee5d/AccountIcon_Female3.png' ]; const neutralProfileImages = [ 'https://images.squarespace-cdn.com/content/v1/6654b2fee26f292de13f1a9d/a31bed02-e0b6-48d6-9d81-8edead9fae30/AccountIcon_StandardUnknown1.png' ]; //show and hide popover let hideTimeout; var popover = document.getElementById('custom-popover'); var popoverMessage = document.getElementById('popover-message'); function showPopover(message) { popoverMessage.textContent = message; popover.classList.add('show'); popover.style.pointerEvents = 'auto'; clearTimeout(hideTimeout); hideTimeout = setTimeout(function() { popover.classList.remove('show'); popover.style.pointerEvents = 'none'; }, 3000); } //to assign new rank every month function calculateMonthsSince(memberSince) { const currentDate = new Date(); const accountDate = new Date(memberSince); const yearsDiff = currentDate.getFullYear() - accountDate.getFullYear(); const monthsDiff = currentDate.getMonth() - accountDate.getMonth(); return (yearsDiff * 12) + monthsDiff; } function getRank(memberSince) { const monthsSince = calculateMonthsSince(memberSince); rankNumber = Math.min(Math.floor(monthsSince / 1) + 1, 33); const rankString = rankNumber + ': ' + rankMap.get(rankNumber); if (oldRank !== rankString) { $('#popoverMessage').off('click'); popoverMessage.style.color = "#ffc700"; showPopover('You have been promoted to ' + rankMap.get(rankNumber) + ', effective immediately'); } return rankString; } //to assign new notes every two weeks function calculateWeeksSince(memberSince) { const currentDate = new Date(); const accountDate = new Date(memberSince); const timeDiff = currentDate - accountDate; const weeksDiff = Math.floor(timeDiff / (1000 * 60 * 60 * 24 * 7)); return weeksDiff; } function getNotes(memberSince) { const weeksSince = calculateWeeksSince(memberSince); const noteNumber = Math.min(Math.floor(weeksSince / 2) + 1, 66); const notesString = notesMap.get(noteNumber); return notesString; } //to calculate remining time until rank update function calculateTimeDifference(date1, date2) { const oneMinute = 60 * 1000; // Milliseconds in a minute const oneHour = 60 * oneMinute; // Milliseconds in an hour const oneDay = 24 * oneHour; // Milliseconds in a day const timeDifference = date2 - date1; const days = Math.floor(timeDifference / oneDay); const hours = Math.floor((timeDifference % oneDay) / oneHour); const minutes = Math.floor((timeDifference % oneHour) / oneMinute); return { days, hours, minutes }; } function timeUntilNextRank(memberSince) { const currentDate = new Date(); const accountDate = new Date(memberSince); let nextRankUpdateYear = currentDate.getFullYear(); let nextRankUpdateMonth = currentDate.getMonth(); let nextRankUpdateDate = new Date(nextRankUpdateYear, nextRankUpdateMonth, accountDate.getDate()); if (currentDate >= nextRankUpdateDate) { nextRankUpdateDate.setMonth(nextRankUpdateDate.getMonth() + 1); } const timeRemaining = calculateTimeDifference(currentDate, nextRankUpdateDate); const daysLeftText = timeRemaining.days === 1 ? 'day' : 'days'; const hoursLeftText = timeRemaining.hours === 1 ? 'hour' : 'hours'; const minutesLeftText = timeRemaining.minutes === 1 ? 'minute' : 'minutes'; return `★ Next rank evaluation is in ${timeRemaining.days} ${daysLeftText}, ${timeRemaining.hours} ${hoursLeftText}, and ${timeRemaining.minutes} ${minutesLeftText}. ★`; } //to get time until next notes briefing function timeUntilNextNote(memberSince) { const currentDate = new Date(); const accountDate = new Date(memberSince); let nextNoteUpdateDate = new Date(accountDate); let currentMonth = currentDate.getMonth(); let daysInMonth = new Date(currentDate.getFullYear(), currentMonth + 1, 0).getDate(); let halfMonth = Math.ceil(daysInMonth / 2); let timeSinceStart = Math.floor((currentDate - accountDate) / (1000 * 60 * 60 * 24 * halfMonth)) * halfMonth; nextNoteUpdateDate.setDate(accountDate.getDate() + timeSinceStart + halfMonth); if (currentDate >= nextNoteUpdateDate) { nextNoteUpdateDate.setDate(nextNoteUpdateDate.getDate() + halfMonth); } const timeRemaining = calculateTimeDifference(currentDate, nextNoteUpdateDate); const daysLeftText = timeRemaining.days === 1 ? 'day' : 'days'; const hoursLeftText = timeRemaining.hours === 1 ? 'hour' : 'hours'; const minutesLeftText = timeRemaining.minutes === 1 ? 'minute' : 'minutes'; return `★ Next briefing is in ${timeRemaining.days} ${daysLeftText}, ${timeRemaining.hours} ${hoursLeftText}, and ${timeRemaining.minutes} ${minutesLeftText}. ★`; } //to set default gender profile pictures function setProfileImage() { if (gender === 'male') { profileImagesSelection = maleProfileImages; } else if (gender === 'female') { profileImagesSelection = femaleProfileImages; } else { profileImagesSelection = neutralProfileImages; } if (!profilePic) { profilePic = profileImagesSelection[0]; } profileImage.style.backgroundImage = `url(${profilePic})`; return profilePic; } //change profile image functionality function showImageControls(){ profileImageControlRight.classList.add('visible'); profileImageControlLeft.classList.add('visible'); } function hideImageControls(){ profileImageControlRight.classList.remove('visible'); profileImageControlLeft.classList.remove('visible'); } profileImageInnerContainer.addEventListener('mouseenter', () => { showImageControls(); }); profileImageInnerContainer.addEventListener('mouseleave', () => { hideImageControls(); }); profileImage.addEventListener('click', () => { showImageControls(); }); profileImageInnerContainer.addEventListener('blur', () => { hideImageControls(); }); profileImageControlRight.addEventListener('click', () => { currentImageIndex = (currentImageIndex + 1) % profileImagesSelection.length; profilePic = profileImagesSelection[currentImageIndex]; profileImage.style.backgroundImage = `url(${profilePic})`; }); profileImageControlLeft.addEventListener('click', () => { currentImageIndex = (currentImageIndex - 1 + profileImagesSelection.length) % profileImagesSelection.length; profilePic = profileImagesSelection[currentImageIndex]; profileImage.style.backgroundImage = `url(${profilePic})`; }); //custom radio buttons functionality const radioButtons = document.querySelectorAll('.dossier-RadioButton'); function handleRadioClick(event) { radioButtons.forEach(button => { button.classList.remove('selected'); }); event.currentTarget.classList.add('selected'); const selectedClass = event.currentTarget.classList[1]; switch (selectedClass) { case 'male': gender = 'male'; break; case 'female': gender = 'female'; break; case 'classified': gender = 'classified'; break; default: gender = 'classified'; break; } setProfileImage(); profilePic = profileImagesSelection[0]; setProfileImage(); } radioButtons.forEach(button => { button.addEventListener('click', handleRadioClick); }); //if logged in onAuthStateChanged(auth, async (user) => { if (user) { handle = user.email; memberSince = user.metadata.creationTime; const userDocRef = doc(db, 'users', user.uid); try { const userDoc = await getDoc(userDocRef); if (userDoc.exists()) { const userData = userDoc.data(); isMaster = userData.Master || false; codename = userData.codename || ''; lastCheckIn = userData.lastStatusChange || 'never'; realName = userData.name || ''; gender = userData.gender || 'classified'; profilePic = userData.avatar || setProfileImage(); cover = userData.cover || ''; expertise = userData.expertise || ''; notes = isMaster ? userData.notes : getNotes(memberSince); //admins briefing is set manually oldRank = userData.rank || 'classified'; rank = isMaster ? userData.rank : getRank(memberSince); //admins rank is set manually accessGranted = true; //when data is retrieved fill in the dossier setProfileImage(); const handleInput = document.getElementById('dossier-handle-input'); handleInput.value = handle; const rankInput = document.getElementById('dossier-rank-input'); rankInput.value = rank; const codenameInput = document.getElementById('dossier-codename-input'); codenameInput.value = codename; const realnameInput = document.getElementById('dossier-realname-input'); realnameInput.value = realName; const passwordInput = document.getElementById('dossier-password-input'); passwordInput.value = '★★★★★★★★'; const coverText = document.getElementById('dossier-cover-text'); coverText.value = cover; const expertiseText = document.getElementById('dossier-expertise-text'); expertiseText.value = expertise; const notesText = document.getElementById('dossier-notes-text'); notesText.value = notes; const recruitedText = document.getElementById('dossier-recruitedText'); recruitedText.innerHTML = '★ Recruited on ' + memberSince + '. ★'; const evaluationText = document.getElementById('dossier-evaluationText'); timeUntilNextRank(memberSince); evaluationText.innerHTML = timeUntilNextRank(memberSince); const briefingText = document.getElementById('dossier-briefingText'); briefingText.innerHTML = timeUntilNextNote(memberSince); radioButtons.forEach(button => { const selectedClass = button.classList[1]; if (gender == selectedClass){ button.classList.add('selected'); return; } }); async function handleDossierUpdate(event) { event.preventDefault(); // PASSWORD CHANGE const newPassword = passwordInput.value.trim(); if (newPassword.includes('★')) { passwordUpdated = true; } else if (newPassword) { try { await updatePassword(auth.currentUser, newPassword); passwordUpdated = true; } catch (error) { console.error('Error updating password:', error); passwordUpdateErrorMsg = 'Error updating password'; if (error.code === 'auth/requires-recent-login') { const credential = EmailAuthProvider.credential( auth.currentUser.email, prompt('Please re-enter your current password for security reasons:') ); try { await reauthenticateWithCredential(auth.currentUser, credential); await updatePassword(auth.currentUser, newPassword); passwordUpdated = true; } catch (reauthError) { console.error('Error during re-authentication:', reauthError); $('#popoverMessage').off('click'); popoverMessage.style.color = "#ea4b1a"; showPopover('Error during re-authentication.'); } } } } else { passwordUpdated = false; passwordUpdateErrorMsg = 'Password was empty'; } //CODENAME CHANGE const newCodename = codenameInput.value.trim(); if (newCodename) { try { if (newCodename.length > 18) { popoverMessage.style.color = "#ea4b1a"; showPopover('Code name must be 18 characters or less.'); } else if (isMaster === false && forbiddenCodenames.includes(newCodename.toLowerCase())) { updateErrorMsg = 'This code name is already assigned to an agent of a higher rank.'; } else { const codenameQuery = query(collection(db, 'users'), where('codename', '==', newCodename)); const querySnapshot = await getDocs(codenameQuery); if (!querySnapshot.empty && userData.codename !== newCodename) { codenameUpdateErrorMsg = 'This code name is already assigned to another agent'; } else { if (userData && userData.lastCodenameChange) { const lastChangeTime = userData.lastCodenameChange.toDate(); const oneWeekInMillis = 7 * 24 * 60 * 60 * 1000; const now = new Date(); if ((now - lastChangeTime) < oneWeekInMillis && isMaster === false) { codenameUpdateErrorMsg = 'You can only change your codename once a week'; } } await setDoc(userDocRef, { codename: newCodename, lastCodenameChange: serverTimestamp() }, { merge: true }); codenameUpdated = true; } } } catch (error) { console.error('Error updating codename:', error); codenameUpdateErrorMsg = 'Failed to update codename'; } } //REAL NAME CHANGE try { const newRealName = realnameInput.value.trim(); if (newRealName) { await setDoc(userDocRef, { name: newRealName }, { merge: true }); realNameUpdated = true; } } catch (error) { console.error('Error updating real name:', error); updateErrorMsg = 'Failed to update real name.'; } //GENDER CHANGE try { if (gender) { await setDoc(userDocRef, { gender: gender }, { merge: true }); genderUpdated = true; } } catch (error) { console.error('Error updating gender:', error); updateErrorMsg = 'Failed to update gender.'; } //COVER CHANGE try { const newCover = coverText.value.trim(); if (newCover) { await setDoc(userDocRef, { cover: newCover }, { merge: true }); coverUpdated = true; } } catch (error) { console.error('Error updating cover:', error); updateErrorMsg = 'Failed to update cover identity.'; } //EXPERTISE CHANGE try { const newExpertise = expertiseText.value.trim(); if (newExpertise) { await setDoc(userDocRef, { expertise: newExpertise }, { merge: true }); expertiseUpdated = true; } } catch (error) { console.error('Error updating expertise:', error); updateErrorMsg = 'Failed to update expertise.'; } //UPDATE RANK IN DATABASE try { if (rank) { await setDoc(userDocRef, { rank: rank }, { merge: true }); } } catch (error) { console.error('Error updating rank:', error); } //UPDATE PROFILE PICTURE IN DATABASE try { if (rank) { await setDoc(userDocRef, { avatar: profilePic }, { merge: true }); } } catch (error) { console.error('Error updating rank:', error); } // Final message after all updates if (!codenameUpdated) { $('#popoverMessage').off('click'); popoverMessage.style.color = "#ea4b1a"; showPopover(codenameUpdateErrorMsg + ', other changes have been approved'); } else if (!realNameUpdated || !genderUpdated || !coverUpdated || !expertiseUpdated) { $('#popoverMessage').off('click'); popoverMessage.style.color = "#ea4b1a"; showPopover(updateErrorMsg); } else if (!passwordUpdated) { $('#popoverMessage').off('click'); popoverMessage.style.color = "#ea4b1a"; showPopover(passwordUpdateErrorMsg + ', other changes have been approved.'); } else { $('#popoverMessage').off('click'); popoverMessage.style.color = "#ffc700"; showPopover('Dossier changes have been approved'); } } const updateButton = document.getElementById('dossier-assignButton'); updateButton.addEventListener('click', handleDossierUpdate); } } catch (error) { accessGranted = false; console.error('Error fetching user data:', error); $('#popoverMessage').off('click'); popoverMessage.style.color = "#ea4b1a"; showPopover('This dossier appears to be highly classified.'); } //if not logged in } else { window.location.href = '/login'; } }); //exit } else { dossierMainContainer.remove(); } }); </script>
CSS: #dossier-MainContainer { width: 100%; max-width: 2200px; display: flex; justify-content: center; } #dossier-Container { min-width: 50%; max-width: 96%; } #dossier-profileImageContainer { display: flex; justify-content: center; } #dossier-profileImageInnerContainer { display: flex; justify-content: center; align-items: center; cursor: default; margin-bottom: 30px; } #dossier-profileImage { height: 200px; width: 200px; margin-right: 0px; background-image: url(https://images.squarespace-cdn.com/content/v1/6654b2fee26f292de13f1a9d/a31bed02-e0b6-48d6-9d81-8edead9fae30/AccountIcon_StandardUnknown1.png); background-size: contain; background-position: center; background-repeat: no-repeat; } .dossier-dataContainer { width: 100%; } .dossier-profileImage-control { transition: all 1s ease; opacity: 0; border-right: 3px solid black !important; border-top: 3px solid black !important; border-image: linear-gradient(45deg, rgba(0,0,0,0) 50%, rgba(0,0,0,1) 100%); border-image-slice: 1; background-size: contain; background-position: center; background-repeat: no-repeat; width: 50px; height: 50px; margin-top: -30px; margin-left: 40px; margin-right: 40px; cursor: pointer; } .dossier-profileImage-control.visible { opacity: 1; } #dossier-profileImage-left { transform: rotate(-135deg); } #dossier-profileImage-right { transform: rotate(45deg); } .dossier-input { width: 100%; background: transparent; border-top: 0; padding-bottom: 5px; padding-left: 10px; padding-right: 10px; outline: none !important; letter-spacing: 1.4px; } .dossier-detail { padding-left: 12px; margin-top: 20px; letter-spacing: 2px; text-transform: uppercase; } .dossier-text { resize: vertical; min-width: 100% !important; min-height: 100px !important; height: 100px; padding-left: 10px; padding-right: 10px; padding-bottom: 10px; letter-spacing: 1.4px; line-height: 27px; } #dossier-genderContainer { border-bottom: solid 2px; border-image: linear-gradient(90deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 18%, rgba(0,0,0,1) 30%, rgba(0,0,0,0) 50%); border-image-slice: 1; letter-spacing: 1.4px; padding-bottom: 5px; } .dossier-OptionContainer { display: flex; align-items: center; } .dossier-RadioButton { width: 25px; height: 25px; margin-right: 10px; margin-left: 10px; background-image: url(https://images.squarespace-cdn.com/content/v1/6654b2fee26f292de13f1a9d/50d8adb5-c7f1-4c9f-b304-73ecf7dda6ac/Targeticon_unselected1.png); background-size: contain; background-position: center; background-repeat: no-repeat; cursor: pointer; margin-bottom: 6px; } .dossier-RadioButton.selected { background-image: url(https://images.squarespace-cdn.com/content/v1/6654b2fee26f292de13f1a9d/68d3d7c4-1097-4510-8932-df745ace48ce/Targeticon1.png); background-size: contain; background-position: center; background-repeat: no-repeat; } #dossier-buttonContainer { display: flex; width: 100%; justify-content: flex-end; } #dossier-assignButton { width: 50%; margin-top: 20px; margin-right: -20px; margin-bottom: 20px; text-align: right; letter-spacing: 3px !important; } #dossier-recruitedContainer { text-align: center; margin-top: 20px; letter-spacing: 2px !important; } #dossier-handle-input, #dossier-rank-input, #dossier-notes-text { color: var(--color3); } //mobile dossier @media only screen and (max-width:790px) { #dossier-assignButton { width: 100%; } #dossier-Container { min-width: 50%; max-width: 100%; padding-right: 20px; } #dossier-profileImage { height: 150px; width: 150px; margin-right: -15px; } }
It looks very pretty, I encourage you to make an account and have a look. You can access the dossier through your account page.
COMMENTS:
Leave a comment: