From cd6f5e44fdd0a5768c18ac3217209e2103d0a103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sim=C3=B3n=20Villafa=C3=B1e?= Date: Sun, 26 Apr 2026 18:31:54 -0300 Subject: [PATCH 01/10] fix(Root): something --- apps/web/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/index.html b/apps/web/index.html index adcff1f..1af890e 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -4,7 +4,7 @@ - Dojoh.dev + dojoh.dev From 9b84d436408e2699bbaa287fca36e24da1764ee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sim=C3=B3n=20Villafa=C3=B1e?= Date: Sun, 3 May 2026 19:52:50 -0300 Subject: [PATCH 02/10] feat(auth): login 50% done + tracking assets with git fls --- .gitattributes | 5 + apps/web/public/black-waves.mp4 | 3 + apps/web/public/favicon.png | Bin 4931 -> 129 bytes apps/web/public/mesh.png | 3 + apps/web/public/rounded.png | Bin 8015 -> 129 bytes apps/web/src/app/routes.ts | 8 +- .../src/domains/auth/components/typewriter.ts | 0 apps/web/src/domains/auth/page.module.css | 185 ++++++++++++++++++ apps/web/src/domains/auth/page.ts | 171 ++++++++++++++++ apps/web/src/domains/home/layout.module.css | 2 +- apps/web/src/domains/home/layout.ts | 20 +- apps/web/src/styles.css | 4 +- packages/shared/src/styles/default.css | 34 ++++ 13 files changed, 427 insertions(+), 8 deletions(-) create mode 100644 .gitattributes create mode 100644 apps/web/public/black-waves.mp4 create mode 100644 apps/web/public/mesh.png create mode 100644 apps/web/src/domains/auth/components/typewriter.ts create mode 100644 apps/web/src/domains/auth/page.module.css diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..24e364c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +*.mp4 filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +*.webp filter=lfs diff=lfs merge=lfs -text +*.jpeg filter=lfs diff=lfs merge=lfs -text diff --git a/apps/web/public/black-waves.mp4 b/apps/web/public/black-waves.mp4 new file mode 100644 index 0000000..c04f231 --- /dev/null +++ b/apps/web/public/black-waves.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd2ee39aa39dcc718279d549447c2c8565a9ef9ec4139b13644f949722e1470a +size 23947501 diff --git a/apps/web/public/favicon.png b/apps/web/public/favicon.png index 8bcc69360911a78558adec3b1ab193ebe95a6dc3..68a76da01278c29579c622c0f31a204ccb13177f 100644 GIT binary patch literal 129 zcmWN_%MHUI5CG9TRnS1pE?{8Y4c~y21S?=4Qhk`qljhFf(fWtiInQwj@oe+)Dq~yL zD{Qg9#W-^mH?rOmAJxm<91wL-GMnVQ)L6WwJ*GeoR+=0}<&XlHLhHE#nQQ{I8c>=n NonoW?GS{S#{Q;ReCFTGC literal 4931 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 LG=9jVRF3@tr7|YW literal 8015 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 C8>TD( diff --git a/apps/web/src/app/routes.ts b/apps/web/src/app/routes.ts index 5c79a17..915cab3 100644 --- a/apps/web/src/app/routes.ts +++ b/apps/web/src/app/routes.ts @@ -2,13 +2,17 @@ import type { XRoute } from '@repo/router'; export default [ { - path: '/', + path: '/c', component: () => import('../domains/home/layout'), children: [ { - path: '/challenges/:id', + path: '/c/challenges/:id', component: () => import('../domains/challenge/page'), }, ], }, + { + path: '/login', + component: () => import('../domains/auth/page'), + }, ] satisfies XRoute[]; diff --git a/apps/web/src/domains/auth/components/typewriter.ts b/apps/web/src/domains/auth/components/typewriter.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/web/src/domains/auth/page.module.css b/apps/web/src/domains/auth/page.module.css new file mode 100644 index 0000000..56aa1fd --- /dev/null +++ b/apps/web/src/domains/auth/page.module.css @@ -0,0 +1,185 @@ +.login { + display: flex; + height: 100vh; + column-gap: 5vw; + padding: 20px; +} + +.leftSide { + width: 70%; + height: 100%; + + position: relative; + + border-radius: 30px; + + overflow: hidden; + + &::before { + content: ''; + + position: absolute; + z-index: 2; + + width: 100%; + height: 100%; + + background-size: cover; + background: linear-gradient( + 180deg, + rgb(0 0 0 / 80%) 0%, + rgb(0 0 0 / 10%) 40%, + rgb(0 0 0 / 10%) 70%, + rgb(0 0 0 / 90%) 100% + ); + } + + video { + user-select: none; + pointer-events: none; + + position: absolute; + top: 0; + left: 0; + z-index: 1; + + width: 100%; + height: 100%; + + object-fit: cover; + } +} + +.logo { + position: absolute; + top: 20px; + left: 20px; + + z-index: 3; + + display: flex; + align-items: center; + column-gap: 8px; + + img { + width: 32px; + height: 32px; + object-fit: cover; + object-position: center; + } + + h1 { + display: flex; + align-items: flex-end; + width: max-content; + font-size: var(--font-xl); + font-weight: 400; + font-family: var(--font-serif); + color: #fff; + + > p { + font-size: var(--font-lg); + color: var(--primary-color); + margin-bottom: 1px; + } + } +} + +.text { + position: absolute; + bottom: 20px; + left: 20px; + + display: flex; + flex-direction: column; + row-gap: 22px; + z-index: 3; + + width: 100%; + max-width: 60%; + + p { + color: var(--foreground-color); + font-family: var(--font-serif); + font-size: 18px; + font-weight: 300; + } +} + +.bubbleCursor { + position: absolute; + + z-index: 4; + + width: 200px; + height: 200px; + + background-color: var(--foreground-color); + border-radius: 100px; + + pointer-events: none; + transition: transform 400ms var(--t-bounce); + transform-origin: center; + mix-blend-mode: difference; +} + +.typewriterContainer { + position: relative; + width: max-content; + display: flex; + flex-direction: row; + + .typewriter { + height: 28px; + width: max-content; + overflow: hidden; + + color: var(--foreground-color); + font-family: var(--font-mono); + font-size: 22px; + font-weight: 700; + + letter-spacing: 0em; + white-space: nowrap; + } + + .cursor { + margin-top: 2px; + margin-left: 4px; + + width: 16px; + height: 22px; + + background-color: var(--foreground-color); + + animation: blink 1s step-start infinite; + + &[data-still-typing='false'] { + animation: none; + } + } +} + +.rightSide { + display: flex; + width: 30%; + height: 100%; + border: 1px solid red; +} + +@keyframes blink { + 0%, + 50% { + opacity: 1; + } + 50.01%, + 100% { + opacity: 0; + } +} + +@media (width <= 768px) { + .text { + max-width: 90%; + } +} diff --git a/apps/web/src/domains/auth/page.ts b/apps/web/src/domains/auth/page.ts index e69de29..fcd9f7a 100644 --- a/apps/web/src/domains/auth/page.ts +++ b/apps/web/src/domains/auth/page.ts @@ -0,0 +1,171 @@ +import { effect } from '@repo/shared/stateful'; + +import styles from './page.module.css'; + +export const metadata = { + title: 'Dojoh.dev – Get access to your account', +}; + +const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +const traverseNodes = (node: NodeListOf): Array => { + const childNodes: Array = []; + node.forEach((child) => { + if (child.childNodes.length > 0) { + childNodes.push(...traverseNodes(child.childNodes)); + } else { + childNodes.push(child); + } + }); + return childNodes; +}; + +export default function component() { + // Typewriter effect + effect(() => { + const typewritter = + document.querySelector('#typewriter'); + const cursor = document.querySelector(`#cursor`); + + if (!cursor) throw new Error('Cursor element not found'); + if (!typewritter) throw new Error('Typewritter element not found'); + + const phrases = [ + 'Join, Play, have fun!', + 'Challenge yourself and others!', + 'Compete with friends and climb the leaderboard!', + ]; + + let currentPhraseIdx = 0; + let currentPhrase = phrases[currentPhraseIdx]; + let currentCharIdx = 0; + let isDeleting = false; + + const typeLoop = async () => { + if (cursor.dataset.stillTyping !== 'false') { + cursor.dataset.stillTyping = 'false'; + } + + if (isDeleting) { + currentCharIdx--; + typewritter.textContent = currentPhrase.slice( + 0, + currentCharIdx + ); + + if (currentCharIdx === 0) { + isDeleting = false; + currentPhraseIdx = (currentPhraseIdx + 1) % phrases.length; + currentPhrase = phrases[currentPhraseIdx]; + } + } else { + currentCharIdx++; + typewritter.textContent = currentPhrase.slice( + 0, + currentCharIdx + ); + + if (currentCharIdx === currentPhrase.length) { + isDeleting = true; + + cursor.dataset.stillTyping = 'true'; + await wait(3000); // Pause to read the full phrase + } + } + + setTimeout(typeLoop, 150); + }; + + typeLoop(); + }); + + // Bubble cursor effect + effect(() => { + const bubbleCursor = + document.querySelector('#bubble-cursor'); + const leftSide = document.querySelector('#left-side'); + + if (!leftSide) throw new Error('Left side element not found'); + if (!bubbleCursor) throw new Error('Bubble cursor element not found'); + + // Keep track of current scale and translate + let currentScale = 0; + const currentTranslate = { x: 0, y: 0 }; + + const updateTransform = () => { + bubbleCursor.style.transform = `translate(${currentTranslate.x}px, ${currentTranslate.y}px) scale(${currentScale})`; + }; + + const setScaleOnly = (scale: number) => { + currentScale = scale; + updateTransform(); + }; + + let isCursorInside = false; + + leftSide.addEventListener('mouseleave', () => { + isCursorInside = false; + document.documentElement.style.cursor = 'default'; + }); + + leftSide.addEventListener('mouseenter', () => { + isCursorInside = true; + document.documentElement.style.cursor = 'none'; + }); + + const FIXED_OFFSET = 20; + + document.addEventListener('mousemove', (e) => { + const elementOnCursor = document.elementFromPoint( + e.clientX, + e.clientY + ); + + const isHeadingOrText = + elementOnCursor && + [ + HTMLHeadingElement, + HTMLParagraphElement, + HTMLImageElement, + ].some((instance) => elementOnCursor instanceof instance); + + currentScale = isCursorInside ? (isHeadingOrText ? 0.25 : 1) : 0; + + const width = bubbleCursor.offsetWidth; + const height = bubbleCursor.offsetHeight; + + currentTranslate.x = e.clientX - width / 2 - FIXED_OFFSET; + currentTranslate.y = e.clientY - height / 2 - FIXED_OFFSET; + + updateTransform(); + }); + }); + + return /*html*/ ` +
+
+ + + + + +
+

Join, Play, have fun!

+ +
+ +

Yorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam eu turpis molestie, dictum est a, mattis tellus. Sed dignissim, metus nec fringilla accumsan.

+
+ + +
+ +
+
+
`; +} diff --git a/apps/web/src/domains/home/layout.module.css b/apps/web/src/domains/home/layout.module.css index fef2eb2..d9314fc 100644 --- a/apps/web/src/domains/home/layout.module.css +++ b/apps/web/src/domains/home/layout.module.css @@ -24,7 +24,7 @@ width: max-content; font-size: var(--font-lg); font-weight: 400; - font-family: var(--font-sans-serif); + font-family: var(--font-serif); color: #fff; span { diff --git a/apps/web/src/domains/home/layout.ts b/apps/web/src/domains/home/layout.ts index 923dde2..9cc744c 100644 --- a/apps/web/src/domains/home/layout.ts +++ b/apps/web/src/domains/home/layout.ts @@ -7,7 +7,7 @@ export default function () { logo

dojoh.dev

- + - + - - + + Login + + + Get started +
diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css index 038a3ad..a8161aa 100644 --- a/apps/web/src/styles.css +++ b/apps/web/src/styles.css @@ -18,7 +18,7 @@ /* Typography */ --font-sans: 'Public Sans', sans-serif; --font-mono: 'Google Sans Code', monospace; - --font-sans-serif: 'Trocchi', serif; + --font-serif: 'Trocchi', serif; --font-xs: 0.75rem; --font-sm: 0.875rem; --font-md: 1rem; @@ -26,6 +26,8 @@ --font-xl: 1.5rem; --font-2xl: 2rem; --line-height: 1.5; + + --t-bounce: cubic-bezier(0.175, 0.885, 0.32, 1.275); } *, diff --git a/packages/shared/src/styles/default.css b/packages/shared/src/styles/default.css index 23ef622..af11ecc 100644 --- a/packages/shared/src/styles/default.css +++ b/packages/shared/src/styles/default.css @@ -19,6 +19,40 @@ button { } } +a[role='button'] { + font-size: var(--font-sm); + font-family: var(--font-sans); + border-radius: var(--radii); + padding-inline: 10px; + padding-block: 8px; + cursor: pointer; + border: none; + transition: + opacity 200ms ease, + transform 100ms ease; + + text-decoration: none; + + &:hover { + opacity: 0.9; + } + + &:active { + transform: scale(0.975); + } +} + +a[variant='solid'][role='button'] { + background-color: var(--foreground-color); + color: var(--black-color); +} + +a[variant='outline'][role='button'] { + background-color: transparent; + border: 1px solid var(--border-color); + color: var(--foreground-color); +} + button[variant='outline'] { background-color: transparent; border: 1px solid var(--border-color); From a7976e7a76201d96e5a5a98b6756e63043047bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sim=C3=B3n=20Villafa=C3=B1e?= Date: Mon, 4 May 2026 00:21:45 -0300 Subject: [PATCH 03/10] feat(auth): login at 70% --- apps/web/@types/svg.d.ts | 9 + apps/web/public/curlyline.svg | 22 ++ apps/web/public/long-curlyline.svg | 40 +++ apps/web/public/social-media.png | 3 + .../web/src/assets/lucid-icons/eye-closed.svg | 1 + apps/web/src/assets/lucid-icons/eye.svg | 1 + .../domains/auth/components/bubble-cursor.ts | 0 .../domains/auth/components/crypto-text.ts | 0 apps/web/src/domains/auth/components/input.ts | 0 .../domains/auth/components/oauth-button.ts | 0 apps/web/src/domains/auth/page.module.css | 286 +++++++++++++++++- apps/web/src/domains/auth/page.ts | 223 +++++++++++++- apps/web/src/domains/home/layout.ts | 4 +- apps/web/src/styles.css | 4 + packages/shared/src/styles/default.css | 4 +- 15 files changed, 575 insertions(+), 22 deletions(-) create mode 100644 apps/web/@types/svg.d.ts create mode 100644 apps/web/public/curlyline.svg create mode 100644 apps/web/public/long-curlyline.svg create mode 100644 apps/web/public/social-media.png create mode 100644 apps/web/src/assets/lucid-icons/eye-closed.svg create mode 100644 apps/web/src/assets/lucid-icons/eye.svg create mode 100644 apps/web/src/domains/auth/components/bubble-cursor.ts create mode 100644 apps/web/src/domains/auth/components/crypto-text.ts create mode 100644 apps/web/src/domains/auth/components/input.ts create mode 100644 apps/web/src/domains/auth/components/oauth-button.ts diff --git a/apps/web/@types/svg.d.ts b/apps/web/@types/svg.d.ts new file mode 100644 index 0000000..e6b8d8a --- /dev/null +++ b/apps/web/@types/svg.d.ts @@ -0,0 +1,9 @@ +declare module '*.svg' { + const content: string; + export default content; +} + +declare module '*.svg?import' { + const content: string; + export default content; +} diff --git a/apps/web/public/curlyline.svg b/apps/web/public/curlyline.svg new file mode 100644 index 0000000..d965277 --- /dev/null +++ b/apps/web/public/curlyline.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/long-curlyline.svg b/apps/web/public/long-curlyline.svg new file mode 100644 index 0000000..b79a3f9 --- /dev/null +++ b/apps/web/public/long-curlyline.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/social-media.png b/apps/web/public/social-media.png new file mode 100644 index 0000000..2c27c59 --- /dev/null +++ b/apps/web/public/social-media.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:061ebf25cda566c84a2549f155ec29682d096d1719de52259ac169f46074a246 +size 9023 diff --git a/apps/web/src/assets/lucid-icons/eye-closed.svg b/apps/web/src/assets/lucid-icons/eye-closed.svg new file mode 100644 index 0000000..29302ef --- /dev/null +++ b/apps/web/src/assets/lucid-icons/eye-closed.svg @@ -0,0 +1 @@ + diff --git a/apps/web/src/assets/lucid-icons/eye.svg b/apps/web/src/assets/lucid-icons/eye.svg new file mode 100644 index 0000000..2d1ebec --- /dev/null +++ b/apps/web/src/assets/lucid-icons/eye.svg @@ -0,0 +1 @@ + diff --git a/apps/web/src/domains/auth/components/bubble-cursor.ts b/apps/web/src/domains/auth/components/bubble-cursor.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/web/src/domains/auth/components/crypto-text.ts b/apps/web/src/domains/auth/components/crypto-text.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/web/src/domains/auth/components/input.ts b/apps/web/src/domains/auth/components/input.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/web/src/domains/auth/components/oauth-button.ts b/apps/web/src/domains/auth/components/oauth-button.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/web/src/domains/auth/page.module.css b/apps/web/src/domains/auth/page.module.css index 56aa1fd..790bacb 100644 --- a/apps/web/src/domains/auth/page.module.css +++ b/apps/web/src/domains/auth/page.module.css @@ -111,8 +111,8 @@ z-index: 4; - width: 200px; - height: 200px; + width: 0px; + height: 0px; background-color: var(--foreground-color); border-radius: 100px; @@ -162,9 +162,283 @@ .rightSide { display: flex; + flex-direction: column; + width: 30%; height: 100%; - border: 1px solid red; + + h1 { + color: var(--foreground-color); + font-family: var(--font-serif); + font-size: 26px; + font-weight: 400; + + margin-top: 50px; + margin-bottom: 20px; + } + + p { + color: var(--grey-color); + font-family: var(--font-sans); + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 20px; + word-break: break-all; + max-width: 100%; + } + + form { + display: flex; + flex-direction: column; + row-gap: 14px; + + margin-top: 40px; + + a { + color: var(--foreground-color); + font-family: var(--font-sans); + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; + text-decoration: none; + text-align: center; + width: 100%; + display: block; + + margin-block: 16px; + + &:hover { + text-decoration: underline; + } + } + + button[type='submit'] { + display: flex; + justify-content: center; + align-items: center; + gap: 10px; + padding: 9px 10px 8px 10px; + + width: 100%; + height: 40px; + + border-radius: 10px; + border: 1px solid #1f0456; + background: rgb(89 22 223 / 70%); + + color: var(--foreground-color); + font-family: var(--font-sans); + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: normal; + + transition: background 200ms linear; + + &:hover { + background: rgb(89 22 223 / 100%); + } + } + } + + .or { + position: relative; + width: 100%; + display: block; + height: 20px; + + background-image: url(/long-curlyline.svg); + background-size: 200%; + background-repeat: no-repeat; + background-position: 0% 50%; + + margin-block: 42px; + + animation: progress-line 30s linear infinite; + + &:hover { + animation-play-state: paused; + } + + &::before { + content: 'OR'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: var(--background-color); + padding: 0 8px; + + color: var(--grey-color); + font-family: var(--font-sans); + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: normal; + } + } +} + +.inputGroup { + display: flex; + flex-direction: column; + row-gap: 12px; + position: relative; + + label { + color: var(--foreground-color); + font-family: var(--font-sans); + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: normal; + } + + > div { + width: 100%; + height: 32px; + display: flex; + + input { + width: 100%; + height: 100%; + + background-color: transparent; + border: 1px solid var(--border-color); + border-radius: var(--radii); + padding-block: 7px; + padding-left: 12px; + + color: var(--foreground-color); + font-family: var(--font-sans); + font-size: 14px; + font-weight: 400; + + &:has(+ span[data-append]) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-right: none; + } + + &:focus { + border-color: var(--primary-color); + outline: none; + + + span[data-append] { + border-color: var(--primary-color); + } + } + } + + > span[data-append] { + background: none; + border: none; + padding: none; + margin: none; + + border: 1px solid var(--border-color); + border-left: none; + border-top-right-radius: var(--radii); + border-bottom-right-radius: var(--radii); + + display: grid; + place-items: center; + + width: 50px; + height: 32px; + + color: var(--grey-color); + font-family: var(--font-sans); + font-size: 14px; + font-weight: 400; + + cursor: pointer; + } + } +} + +.stackY { + display: flex; + flex-direction: column; + row-gap: 14px; +} + +.oauthButton { + background-color: transparent; + + display: flex; + justify-content: center; + align-items: center; + gap: 10px; + padding: 9px 10px 8px 10px; + + width: 100%; + height: 40px; + + border-radius: var(--radii); + border: 1px solid var(--grey-600-color); + + color: var(--foreground-color); + font-family: var(--font-sans); + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: normal; + + transition: background-color 200ms linear; + + div[aria-label] { + display: inline-block; + width: 16px; + height: 16px; + + background-image: url('/social-media.png'); + background-size: 32px 32px; + background-repeat: no-repeat; + } + + &:hover { + background-color: rgb(255 255 255 / 10%); + } +} + +.oauthButton[data-provider='google'] { + div[aria-label] { + background-position: 0 0; + } +} + +.oauthButton[data-provider='github'] { + div[aria-label] { + background-position: -16px 0; + } +} + +.oauthButton[data-provider='discord'] { + div[aria-label] { + background-position: 0 -16px; + } +} + +.signUpPrompt { + color: var(--foreground-color); + font-family: var(--font-sans); + font-size: 14px; + font-weight: 400; + text-align: center; + + margin-top: 26px; + + a { + color: var(--primary-color); + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } } @keyframes blink { @@ -178,6 +452,12 @@ } } +@keyframes progress-line { + 100% { + background-position-x: 100%; + } +} + @media (width <= 768px) { .text { max-width: 90%; diff --git a/apps/web/src/domains/auth/page.ts b/apps/web/src/domains/auth/page.ts index fcd9f7a..b893b1c 100644 --- a/apps/web/src/domains/auth/page.ts +++ b/apps/web/src/domains/auth/page.ts @@ -1,5 +1,7 @@ import { effect } from '@repo/shared/stateful'; +import eyeClosedIcon from '../../assets/lucid-icons/eye-closed.svg?import'; +import eyeIcon from '../../assets/lucid-icons/eye.svg?import'; import styles from './page.module.css'; export const metadata = { @@ -8,16 +10,82 @@ export const metadata = { const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); -const traverseNodes = (node: NodeListOf): Array => { - const childNodes: Array = []; - node.forEach((child) => { - if (child.childNodes.length > 0) { - childNodes.push(...traverseNodes(child.childNodes)); - } else { - childNodes.push(child); - } - }); - return childNodes; +const encryptText = async ( + element: HTMLElement, + finalText: string, + opt: { tickDelay: number; wait: number } = { + tickDelay: 50, + wait: 500, + } +) => { + const symbols = '1 0'; + + const getRandomSymbol = () => + symbols[Math.floor(Math.random() * symbols.length)]; + + const originalText = element.textContent || ''; + const maxLength = Math.max(originalText.length, finalText.length); + + const paddedFinal = finalText.padEnd(maxLength, ' '); + + // Current visible chars + const current = originalText.padEnd(maxLength, ' ').split(''); + + // 1. Randomly replace chars with symbols + const touchedIndices = new Set(); + + const encryptionLoop = async (): Promise => + new Promise((resolve) => { + const tick = () => { + if (touchedIndices.size >= maxLength) { + resolve(); + return; + } + + let idx: number; + + do { + idx = Math.floor(Math.random() * maxLength); + } while (touchedIndices.has(idx)); + + current[idx] = getRandomSymbol(); + element.textContent = current.join(''); + touchedIndices.add(idx); + + setTimeout(tick, opt.tickDelay); + }; + + tick(); + }); + + const decryptionLoop = async (): Promise => + new Promise((resolve) => { + const tick = () => { + if (touchedIndices.size === 0) { + element.textContent = finalText; + resolve(); + return; + } + + let idx: number; + + do { + idx = Math.floor(Math.random() * maxLength); + } while (!touchedIndices.has(idx)); + + current[idx] = paddedFinal[idx]; + element.textContent = current.join(''); + touchedIndices.delete(idx); + + setTimeout(tick, opt.tickDelay); + }; + + tick(); + }); + + await encryptionLoop(); + await wait(opt.wait); // Stupid grace time? + await decryptionLoop(); }; export default function component() { @@ -96,11 +164,6 @@ export default function component() { bubbleCursor.style.transform = `translate(${currentTranslate.x}px, ${currentTranslate.y}px) scale(${currentScale})`; }; - const setScaleOnly = (scale: number) => { - currentScale = scale; - updateTransform(); - }; - let isCursorInside = false; leftSide.addEventListener('mouseleave', () => { @@ -111,6 +174,8 @@ export default function component() { leftSide.addEventListener('mouseenter', () => { isCursorInside = true; document.documentElement.style.cursor = 'none'; + bubbleCursor.style.height = '200px'; + bubbleCursor.style.width = '200px'; }); const FIXED_OFFSET = 20; @@ -141,6 +206,72 @@ export default function component() { }); }); + // Password visibility toggle + effect(() => { + const toggleButton = document.querySelector( + '#toggle-password-visibility' + ); + const passwordInput = + document.querySelector('#password'); + + if (!toggleButton) throw new Error('Toggle button not found'); + if (!passwordInput) throw new Error('Password input not found'); + + toggleButton.addEventListener('click', () => { + const isPasswordVisible = passwordInput.type === 'text'; + passwordInput.type = isPasswordVisible ? 'password' : 'text'; + toggleButton.querySelector('img')!.src = isPasswordVisible + ? eyeIcon + : eyeClosedIcon; + toggleButton.setAttribute( + 'aria-label', + isPasswordVisible ? 'Show password' : 'Hide password' + ); + }); + }); + + // Switch to sign-up page (encryption animation) + effect(() => { + const signUpLink = + document.querySelector('#redirect-link'); + const title = document.querySelector('#title'); + const subtitle = + document.querySelector('#subtitle'); + + if (!signUpLink) throw new Error('Sign-up link not found'); + + signUpLink.addEventListener('click', (e) => { + e.preventDefault(); + }); + + const titles = ['Welcome back!', 'Join the battle']; + const subtitles = [ + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam eu turpis molestie, dictum est a, mattis tellus.', + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ac diam sit amet quam vehicula elementum sed.', + ]; + const links = ['Join the battle', 'Back to login']; + + let currentIndex = 0; + + signUpLink.addEventListener('click', () => { + currentIndex = (currentIndex + 1) % titles.length; + + encryptText(title!, titles[currentIndex], { + tickDelay: 30, + wait: 300, + }); + // Subtitle has a longer text, so we give it more time to encrypt/decrypt for better effect + encryptText(subtitle!, subtitles[currentIndex], { + tickDelay: 8, + wait: 50, + }); + encryptText(signUpLink, links[currentIndex], { + tickDelay: 30, + wait: 300, + }); + }); + }); + return /*html*/ `
@@ -166,6 +297,68 @@ export default function component() {
+

Welcome back!

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam eu turpis molestie, dictum est a, mattis tellus. +

+ +
+
+ +
+ +
+
+ +
+ +
+ + + Toggle password visibility + +
+
+ + Forgot password? + + +
+ + + +
+ + + + + +
+ +

+ Ready to prove your skills? + + Join the battle. + +

`; } diff --git a/apps/web/src/domains/home/layout.ts b/apps/web/src/domains/home/layout.ts index 9cc744c..c21c5b4 100644 --- a/apps/web/src/domains/home/layout.ts +++ b/apps/web/src/domains/home/layout.ts @@ -20,14 +20,14 @@ export default function () { Login Get started diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css index a8161aa..4aeddf8 100644 --- a/apps/web/src/styles.css +++ b/apps/web/src/styles.css @@ -9,7 +9,11 @@ --foreground-color: hsl(0, 0%, 94%); --background-color: hsl(0, 0%, 4%); --border-color: hsl(0, 0%, 24%); + --black-color: hsl(0, 0%, 13%); + --white-color: hsl(0, 0%, 100%); + --grey-color: hsl(0, 0%, 74.12%); + --grey-600-color: hsl(0, 0%, 29%); /* Layout */ --radii: 10px; diff --git a/packages/shared/src/styles/default.css b/packages/shared/src/styles/default.css index af11ecc..138fd2f 100644 --- a/packages/shared/src/styles/default.css +++ b/packages/shared/src/styles/default.css @@ -1,4 +1,4 @@ -button { +button[data-preset='default'] { font-size: var(--font-sm); font-family: var(--font-sans); border-radius: var(--radii); @@ -19,7 +19,7 @@ button { } } -a[role='button'] { +a[role='button'][data-preset='default'] { font-size: var(--font-sm); font-family: var(--font-sans); border-radius: var(--radii); From 8ce061d4ac16bd37b78c60ba462da144e261d7e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sim=C3=B3n=20Villafa=C3=B1e?= Date: Wed, 6 May 2026 23:45:19 -0300 Subject: [PATCH 04/10] fix(Root): migrate to solidjs + basic auth form working --- .prettierrc.json | 21 +- apps/challenge-mf/eslint.config.mjs | 4 - apps/challenge-mf/lib/entry-client.tsx | 7 - apps/challenge-mf/lib/entry-server.tsx | 6 - apps/challenge-mf/package.json | 31 - apps/challenge-mf/src/App.tsx | 17 - .../templates/code-editor/index.module.css | 75 - .../templates/code-editor/index.tsx | 63 - .../templates/summary/index.module.css | 40 - .../components/templates/summary/index.tsx | 80 - .../templates/test-runner/index.module.css | 78 - .../templates/test-runner/index.tsx | 63 - .../src/components/ui/badge/index.module.css | 18 - .../src/components/ui/badge/index.tsx | 18 - .../src/components/ui/grid/index.module.css | 25 - .../src/components/ui/grid/index.tsx | 112 -- .../src/services/readme/index.service.ts | 207 --- .../src/services/readme/styles.css | 229 --- apps/challenge-mf/src/types/css-module.d.ts | 1 - apps/challenge-mf/tsconfig.json | 11 - apps/challenge-mf/vite.config.js | 51 - .../bubble-cursor.ts => docs/.gitkeep} | 0 apps/platform/.gitignore | 28 + apps/{web => platform}/@types/css-module.d.ts | 0 apps/{web => platform}/@types/svg.d.ts | 0 apps/platform/README.md | 36 + apps/{web => platform}/eslint.config.mjs | 0 apps/{web => platform}/index.html | 6 +- apps/platform/package.json | 30 + apps/platform/pages/-/game.tsx | 3 + apps/platform/pages/-/home.tsx | 51 + .../pages/-/layout/index.module.css} | 0 apps/platform/pages/-/layout/index.tsx | 24 + .../pages/login/index.module.css} | 203 +-- apps/platform/pages/login/index.tsx | 481 ++++++ apps/platform/pnpm-lock.yaml | 1346 +++++++++++++++++ apps/platform/public/black-waves.mp4 | 3 + apps/{web => platform}/public/curlyline.svg | 0 apps/{web => platform}/public/favicon.png | 0 apps/{web => platform}/public/favicon.svg | 0 .../public/long-curlyline.svg | 0 apps/{web => platform}/public/mesh.png | 0 apps/{web => platform}/public/rounded.png | 0 apps/{web => platform}/public/rounded.svg | 0 .../{web => platform}/public/social-media.png | 0 .../molescules/oauth-links/discord.tsx | 30 + .../molescules/oauth-links/github.tsx | 32 + .../molescules/oauth-links/google.tsx | 34 + .../molescules/oauth-links/index.module.css | 56 + .../components/ui/spinner/index.module.css | 148 ++ .../src/components/ui/spinner/index.tsx | 5 + .../input.ts => platform/src/hooks/.gitkeep} | 0 apps/platform/src/index.tsx | 42 + apps/platform/src/lib/constants.ts | 5 + apps/platform/src/lib/env.ts | 7 + apps/platform/src/lib/helpers/cookies.ts | 66 + apps/platform/src/services/auth.service.ts | 79 + apps/platform/src/styles.css | 9 + apps/platform/tsconfig.json | 35 + apps/platform/vite.config.ts | 18 + apps/web/package.json | 35 - apps/web/public/black-waves.mp4 | 3 - apps/web/src/app/bootstrap.ts | 14 - apps/web/src/app/middleware.ts | 18 - apps/web/src/app/routes.ts | 18 - .../web/src/assets/lucid-icons/eye-closed.svg | 1 - apps/web/src/assets/lucid-icons/eye.svg | 1 - .../src/domains/auth/components/typewriter.ts | 0 apps/web/src/domains/auth/page.ts | 364 ----- apps/web/src/domains/challenge/page.ts | 39 - apps/web/src/domains/challenge/styles.css | 3 - apps/web/src/domains/home/layout.module.css | 76 - apps/web/src/domains/home/layout.ts | 40 - apps/web/src/main.ts | 7 - apps/web/src/styles.css | 54 - apps/web/tsconfig.json | 19 - apps/web/vite.config.js | 32 - .../oauth-button.ts => website/.gitkeep} | 0 package.json | 2 +- packages/infra/eslint.config.mjs | 4 - packages/infra/package.json | 3 - packages/infra/src/http/api-client.ts | 8 - packages/router/eslint.config.mjs | 4 - packages/router/package.json | 25 - packages/router/src/defaults/404.ts | 3 - packages/router/src/defaults/500.ts | 3 - packages/router/src/index.ts | 267 ---- packages/router/src/types.ts | 54 - packages/router/tsconfig.build.json | 11 - packages/router/tsconfig.json | 5 - packages/shared/package.json | 16 +- packages/shared/src/lib/anim.ts | 90 ++ packages/shared/src/lib/clock.ts | 2 + packages/shared/src/lib/dom.ts | 13 + packages/shared/src/lib/double-dollar.ts | 9 - packages/shared/src/lib/stateful.ts | 3 - .../shared/src/stylesheets/animations.css | 37 + .../src/{styles => stylesheets}/default.css | 5 + .../src/{styles => stylesheets}/reset.css | 1 + packages/shared/src/stylesheets/typo.css | 3 + packages/shared/src/stylesheets/variables.css | 118 ++ packages/shared/tsconfig.json | 3 + pnpm-lock.yaml | 527 +++++-- 103 files changed, 3340 insertions(+), 2534 deletions(-) delete mode 100644 apps/challenge-mf/eslint.config.mjs delete mode 100644 apps/challenge-mf/lib/entry-client.tsx delete mode 100644 apps/challenge-mf/lib/entry-server.tsx delete mode 100755 apps/challenge-mf/package.json delete mode 100644 apps/challenge-mf/src/App.tsx delete mode 100644 apps/challenge-mf/src/components/templates/code-editor/index.module.css delete mode 100644 apps/challenge-mf/src/components/templates/code-editor/index.tsx delete mode 100644 apps/challenge-mf/src/components/templates/summary/index.module.css delete mode 100644 apps/challenge-mf/src/components/templates/summary/index.tsx delete mode 100644 apps/challenge-mf/src/components/templates/test-runner/index.module.css delete mode 100644 apps/challenge-mf/src/components/templates/test-runner/index.tsx delete mode 100644 apps/challenge-mf/src/components/ui/badge/index.module.css delete mode 100644 apps/challenge-mf/src/components/ui/badge/index.tsx delete mode 100644 apps/challenge-mf/src/components/ui/grid/index.module.css delete mode 100644 apps/challenge-mf/src/components/ui/grid/index.tsx delete mode 100644 apps/challenge-mf/src/services/readme/index.service.ts delete mode 100644 apps/challenge-mf/src/services/readme/styles.css delete mode 100644 apps/challenge-mf/src/types/css-module.d.ts delete mode 100644 apps/challenge-mf/tsconfig.json delete mode 100644 apps/challenge-mf/vite.config.js rename apps/{web/src/domains/auth/components/bubble-cursor.ts => docs/.gitkeep} (100%) create mode 100644 apps/platform/.gitignore rename apps/{web => platform}/@types/css-module.d.ts (100%) rename apps/{web => platform}/@types/svg.d.ts (100%) create mode 100644 apps/platform/README.md rename apps/{web => platform}/eslint.config.mjs (100%) rename apps/{web => platform}/index.html (61%) create mode 100644 apps/platform/package.json create mode 100644 apps/platform/pages/-/game.tsx create mode 100644 apps/platform/pages/-/home.tsx rename apps/{web/src/domains/auth/components/crypto-text.ts => platform/pages/-/layout/index.module.css} (100%) create mode 100644 apps/platform/pages/-/layout/index.tsx rename apps/{web/src/domains/auth/page.module.css => platform/pages/login/index.module.css} (77%) create mode 100644 apps/platform/pages/login/index.tsx create mode 100644 apps/platform/pnpm-lock.yaml create mode 100644 apps/platform/public/black-waves.mp4 rename apps/{web => platform}/public/curlyline.svg (100%) rename apps/{web => platform}/public/favicon.png (100%) rename apps/{web => platform}/public/favicon.svg (100%) rename apps/{web => platform}/public/long-curlyline.svg (100%) rename apps/{web => platform}/public/mesh.png (100%) rename apps/{web => platform}/public/rounded.png (100%) rename apps/{web => platform}/public/rounded.svg (100%) rename apps/{web => platform}/public/social-media.png (100%) create mode 100644 apps/platform/src/components/molescules/oauth-links/discord.tsx create mode 100644 apps/platform/src/components/molescules/oauth-links/github.tsx create mode 100644 apps/platform/src/components/molescules/oauth-links/google.tsx create mode 100644 apps/platform/src/components/molescules/oauth-links/index.module.css create mode 100644 apps/platform/src/components/ui/spinner/index.module.css create mode 100644 apps/platform/src/components/ui/spinner/index.tsx rename apps/{web/src/domains/auth/components/input.ts => platform/src/hooks/.gitkeep} (100%) create mode 100644 apps/platform/src/index.tsx create mode 100644 apps/platform/src/lib/constants.ts create mode 100644 apps/platform/src/lib/env.ts create mode 100644 apps/platform/src/lib/helpers/cookies.ts create mode 100644 apps/platform/src/services/auth.service.ts create mode 100644 apps/platform/src/styles.css create mode 100644 apps/platform/tsconfig.json create mode 100644 apps/platform/vite.config.ts delete mode 100644 apps/web/package.json delete mode 100644 apps/web/public/black-waves.mp4 delete mode 100644 apps/web/src/app/bootstrap.ts delete mode 100644 apps/web/src/app/middleware.ts delete mode 100644 apps/web/src/app/routes.ts delete mode 100644 apps/web/src/assets/lucid-icons/eye-closed.svg delete mode 100644 apps/web/src/assets/lucid-icons/eye.svg delete mode 100644 apps/web/src/domains/auth/components/typewriter.ts delete mode 100644 apps/web/src/domains/auth/page.ts delete mode 100644 apps/web/src/domains/challenge/page.ts delete mode 100644 apps/web/src/domains/challenge/styles.css delete mode 100644 apps/web/src/domains/home/layout.module.css delete mode 100644 apps/web/src/domains/home/layout.ts delete mode 100644 apps/web/src/main.ts delete mode 100644 apps/web/src/styles.css delete mode 100644 apps/web/tsconfig.json delete mode 100644 apps/web/vite.config.js rename apps/{web/src/domains/auth/components/oauth-button.ts => website/.gitkeep} (100%) delete mode 100644 packages/infra/eslint.config.mjs delete mode 100644 packages/infra/package.json delete mode 100644 packages/infra/src/http/api-client.ts delete mode 100644 packages/router/eslint.config.mjs delete mode 100644 packages/router/package.json delete mode 100644 packages/router/src/defaults/404.ts delete mode 100644 packages/router/src/defaults/500.ts delete mode 100644 packages/router/src/index.ts delete mode 100644 packages/router/src/types.ts delete mode 100644 packages/router/tsconfig.build.json delete mode 100644 packages/router/tsconfig.json create mode 100644 packages/shared/src/lib/anim.ts create mode 100644 packages/shared/src/lib/clock.ts create mode 100644 packages/shared/src/lib/dom.ts delete mode 100644 packages/shared/src/lib/double-dollar.ts delete mode 100644 packages/shared/src/lib/stateful.ts create mode 100644 packages/shared/src/stylesheets/animations.css rename packages/shared/src/{styles => stylesheets}/default.css (95%) rename packages/shared/src/{styles => stylesheets}/reset.css (83%) create mode 100644 packages/shared/src/stylesheets/typo.css create mode 100644 packages/shared/src/stylesheets/variables.css diff --git a/.prettierrc.json b/.prettierrc.json index a2646f2..9b6be53 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -4,25 +4,18 @@ "trailingComma": "es5", "singleQuote": true, "plugins": ["@ianvs/prettier-plugin-sort-imports"], - "importOrderParserPlugins": [ - "typescript", - "decorators" - ], + "importOrderParserPlugins": ["typescript", "decorators"], "importOrder": [ "", "", + "^@repo/(.*)$", "", + "^@/(?!.*\\?import$)(.*)$", + "", + "^[.](?!.*\\.css$)", "", - "^@bootstrap/(.*)$", - "^@config/(.*)$", - "^@controllers/(.*)$", - "^@database/(.*)$", - "^@core/(.*)$", - "^@exceptions/(.*)$", - "^@lib/(.*)$", - "^@routes/(.*)$", - "^@validations/(.*)$", "", - "^[.]" + "\\?import$", + "^.*\\.css$" ] } diff --git a/apps/challenge-mf/eslint.config.mjs b/apps/challenge-mf/eslint.config.mjs deleted file mode 100644 index 8e74380..0000000 --- a/apps/challenge-mf/eslint.config.mjs +++ /dev/null @@ -1,4 +0,0 @@ -import { config } from '@repo/eslint-config/react'; - -/** @type {import("eslint").Linter.Config} */ -export default config; diff --git a/apps/challenge-mf/lib/entry-client.tsx b/apps/challenge-mf/lib/entry-client.tsx deleted file mode 100644 index 8c6ce63..0000000 --- a/apps/challenge-mf/lib/entry-client.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { createRoot } from 'react-dom/client'; -import App from '../src/App'; - -export function mountApp(container: HTMLElement, props?: any) { - const root = createRoot(container); - root.render(); -} diff --git a/apps/challenge-mf/lib/entry-server.tsx b/apps/challenge-mf/lib/entry-server.tsx deleted file mode 100644 index 09111f6..0000000 --- a/apps/challenge-mf/lib/entry-server.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { renderToString } from 'react-dom/server'; -import App from '../src/App'; - -export function render() { - return renderToString(); -} diff --git a/apps/challenge-mf/package.json b/apps/challenge-mf/package.json deleted file mode 100755 index 1cd3121..0000000 --- a/apps/challenge-mf/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@dojoh-dev/challenge-mf", - "scripts": { - "dev": "vite dev --port 4000", - "build": "tsc && vite build", - "preview": "vite preview --port 4000", - "check-types": "tsc --noEmit", - "lint": "eslint src/ --fix --ext .ts,.tsx" - }, - "sideEffects": [ - "**/*.css" - ], - "dependencies": { - "@repo/shared": "workspace:*", - "lucide-react": "^0.575.0", - "react": "19.x", - "react-dom": "19.x", - "vite": "^7.0.0" - }, - "devDependencies": { - "@repo/eslint-config": "workspace:*", - "@repo/typescript-config": "workspace:*", - "@types/node": "24.x", - "@types/react": "19.x", - "@types/react-dom": "19.x", - "@vitejs/plugin-react": "^5.1.4", - "eslint": "^10.0.2", - "typescript": "~5.9.3", - "web": "link:apps/web" - } -} diff --git a/apps/challenge-mf/src/App.tsx b/apps/challenge-mf/src/App.tsx deleted file mode 100644 index 1b5cace..0000000 --- a/apps/challenge-mf/src/App.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import Summary from './components/templates/summary'; -import TestRunner from './components/templates/test-runner'; -import CodeEditor from './components/templates/code-editor'; -import Grid from './components/ui/grid'; - -import '@repo/shared/styles/default.css'; -import '@repo/shared/styles/reset.css'; - -export default function App() { - return ( - - - - - - ); -} diff --git a/apps/challenge-mf/src/components/templates/code-editor/index.module.css b/apps/challenge-mf/src/components/templates/code-editor/index.module.css deleted file mode 100644 index cce3250..0000000 --- a/apps/challenge-mf/src/components/templates/code-editor/index.module.css +++ /dev/null @@ -1,75 +0,0 @@ -.editorHero { - width: 100%; - height: 100%; - - grid-area: code; - background-color: var(--card-color); - border-radius: var(--radii); - border: 1px solid var(--border-color); -} - -.editorBody { - display: flex; - flex-direction: row; - width: 100%; - height: calc(100% - 50px); - overflow-y: auto; - - ul { - list-style: none; - padding: 10px; - margin: 0; - - li { - text-align: right; - font-family: var(--font-sans); - font-size: var(--font-md); - color: var(--secondary-color); - line-height: 1.5; - } - } - - textarea { - width: 100%; - height: 100%; - padding: 10px; - font-family: var(--font-mono); - font-size: var(--font-md); - color: var(--foreground-color); - resize: none; - border: none; - background: transparent; - outline: none; - line-height: 1.5; - } -} - -.editorHeader { - height: 50px; - display: flex; - align-items: center; - justify-content: space-between; - column-gap: 8px; - border-bottom: 1px solid var(--border-color); - padding-inline: 10px; - - ul { - display: flex; - align-items: center; - justify-content: center; - column-gap: 6px; - list-style: none; - - li { - display: grid; - place-items: center; - } - } - - h3 { - font-weight: 500; - font-family: var(--font-sans); - font-size: var(--font-sm); - color: var(--foreground-color); - } -} diff --git a/apps/challenge-mf/src/components/templates/code-editor/index.tsx b/apps/challenge-mf/src/components/templates/code-editor/index.tsx deleted file mode 100644 index 01ffde1..0000000 --- a/apps/challenge-mf/src/components/templates/code-editor/index.tsx +++ /dev/null @@ -1,63 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { Bolt, Braces } from 'lucide-react'; - -import styles from './index.module.css'; - -const DEFAULT_CODE = `/** - * Returns the number that appears only once. - * All other numbers appear exactly twice. - */ -export function findUnique(nums: number[]): number { - // TODO: Implement your solution here -}`; - -export default function CodeEditor() { - const [code, setCode] = useState(DEFAULT_CODE); - - const lines = code.split('\n').length; - - const handleChange = (e: React.ChangeEvent) => { - setCode(e.target.value); - }; - - return ( -
-
-
    -
  • - -
  • -
  • -

    TypeScript

    -
  • -
- -
    -
  • - -
  • -
-
- -
-
    - {Array.from({ length: lines }, (_, i) => ( -
  • {i + 1}
  • - ))} -
-