diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx index c2a1157..e320943 100644 --- a/apps/client/src/App.tsx +++ b/apps/client/src/App.tsx @@ -1,14 +1,15 @@ -import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; +import { BrowserRouter, Routes, Route } from "react-router-dom"; import RoomPage from "@/pages/room/RoomPage"; import NotFoundPage from "@/pages/not-found/NotFoundPage"; +import HomePage from "@/pages/home/HomePage"; function App() { return ( - } /> - } /> - } /> + } /> + } /> + } /> ); diff --git a/apps/client/src/pages/home/HomePage.tsx b/apps/client/src/pages/home/HomePage.tsx new file mode 100644 index 0000000..f42f81e --- /dev/null +++ b/apps/client/src/pages/home/HomePage.tsx @@ -0,0 +1,15 @@ +import { Hero } from "./sections/Hero"; +import { ActionCards } from "./sections/ActionCards"; +import { FeatureCards } from "./sections/FeatureCards"; + +export default function MainPage() { + return ( +
+
+ + + +
+
+ ); +} diff --git a/apps/client/src/pages/home/cards/ActionCard.tsx b/apps/client/src/pages/home/cards/ActionCard.tsx new file mode 100644 index 0000000..a2fab09 --- /dev/null +++ b/apps/client/src/pages/home/cards/ActionCard.tsx @@ -0,0 +1,49 @@ +import type { LucideIcon } from "lucide-react"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/shared/ui/card"; +import { cardColorSchemes } from "../constants/card-color-schemes"; + +interface ActionCardProps { + icon: LucideIcon; + title: string; + description: string; + colorKey: string; + children?: React.ReactNode; +} + +export function ActionCard({ + icon: Icon, + title, + description, + colorKey, + children, +}: ActionCardProps) { + const colors = cardColorSchemes[colorKey]; + + return ( + + +
+ +
+ + {title} + + {description} + +
+ + {children} + +
+ ); +} diff --git a/apps/client/src/pages/home/cards/FeatureCard.tsx b/apps/client/src/pages/home/cards/FeatureCard.tsx new file mode 100644 index 0000000..6946459 --- /dev/null +++ b/apps/client/src/pages/home/cards/FeatureCard.tsx @@ -0,0 +1,36 @@ +import type { LucideIcon } from "lucide-react"; +import { Card, CardDescription, CardHeader, CardTitle } from "@/shared/ui/card"; +import { cardColorSchemes } from "../constants/card-color-schemes"; + +interface FeatureCardProps { + icon: LucideIcon; + title: string; + description: string; + colorKey: string; +} + +export function FeatureCard({ + icon: Icon, + title, + description, + colorKey, +}: FeatureCardProps) { + const colors = cardColorSchemes[colorKey]; + + return ( + + +
+ +
+ + {title} + + {description} + +
+
+ ); +} diff --git a/apps/client/src/pages/home/components/RoomCodeInput.tsx b/apps/client/src/pages/home/components/RoomCodeInput.tsx new file mode 100644 index 0000000..9d4349e --- /dev/null +++ b/apps/client/src/pages/home/components/RoomCodeInput.tsx @@ -0,0 +1,130 @@ +import { useRef } from "react"; +import type { KeyboardEvent, ClipboardEvent } from "react"; + +export const ROOM_CODE_LENGTH = 6; + +interface RoomCodeInputProps { + value: string[]; + onChange: (code: string[]) => void; + hasError?: boolean; + onSubmit?: () => void; + length?: number; +} + +export function RoomCodeInput({ + value, + onChange, + hasError = false, + onSubmit, + length = ROOM_CODE_LENGTH, +}: RoomCodeInputProps) { + const inputRefs = useRef<(HTMLInputElement | null)[]>([]); + + const handleChange = (index: number, inputValue: string) => { + if (inputValue !== "" && !isValidRoomCodeChar(inputValue)) return; + + const newCode = [...value]; + newCode[index] = inputValue; + onChange(newCode); + + // Move to next field after typing a character + if (inputValue !== "" && index < length - 1) { + inputRefs.current[index + 1]?.focus(); + } + }; + + const handleKeyDown = (index: number, e: KeyboardEvent) => { + const inputValue = (e.target as HTMLInputElement).value; + + // Backspace handling + // If current field is empty, move to previous field and clear it + if (e.key === "Backspace") { + if (inputValue === "" && index > 0) { + e.preventDefault(); + const newCode = [...value]; + newCode[index - 1] = ""; + onChange(newCode); + inputRefs.current[index - 1]?.focus(); + } + } + + // Left arrow + if (e.key === "ArrowLeft" && index > 0) { + e.preventDefault(); + inputRefs.current[index - 1]?.focus(); + } + + // Right arrow + if (e.key === "ArrowRight" && index < length - 1) { + e.preventDefault(); + inputRefs.current[index + 1]?.focus(); + } + + // Enter key to submit + if (e.key === "Enter") { + const roomCode = value.join(""); + if (roomCode.length === length && onSubmit) { + onSubmit(); + } + } + }; + + // Paste handling + const handlePaste = (index: number, e: ClipboardEvent) => { + e.preventDefault(); + + const pastedData = e.clipboardData + .getData("text") + .toUpperCase() + .slice(0, length); + + // Filter only alphanumeric characters + const validChars = pastedData + .split("") + .filter((char) => isValidRoomCodeChar(char)); + + if (validChars.length === 0) return; + + const newCode = [...value]; + + // Fill from current index + validChars.forEach((char, i) => { + const targetIndex = index + i; + if (targetIndex < length) { + newCode[targetIndex] = char; + } + }); + + onChange(newCode); + + // Set new focus + const nextIndex = Math.min(index + validChars.length, length - 1); + inputRefs.current[nextIndex]?.focus(); + }; + + return ( +
+ {value.map((digit, index) => ( + { + inputRefs.current[index] = el; + }} + type="text" + maxLength={1} + value={digit} + onChange={(e) => handleChange(index, e.target.value)} + onKeyDown={(e) => handleKeyDown(index, e)} + onPaste={(e) => handlePaste(index, e)} + className={`w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 text-center text-base sm:text-xl md:text-2xl font-semibold font-mono border-2 ${ + hasError ? "border-red-500" : "border-gray-400" + } focus:border-gray-900 focus:outline-none transition-colors uppercase caret-transparent`} + /> + ))} +
+ ); +} + +const isValidRoomCodeChar = (char: string): boolean => { + return /^[a-zA-Z0-9]$/.test(char); +}; diff --git a/apps/client/src/pages/home/constants/card-color-schemes.ts b/apps/client/src/pages/home/constants/card-color-schemes.ts new file mode 100644 index 0000000..d72d61c --- /dev/null +++ b/apps/client/src/pages/home/constants/card-color-schemes.ts @@ -0,0 +1,61 @@ +export interface CardColorScheme { + cardBg: string; + iconBg: string; + borderColor: string; + iconColor: string; + hoverCardBg: string; + hoverBorderColor: string; +} + +const blue: CardColorScheme = { + cardBg: "bg-blue-50", + iconBg: "bg-blue-100", + borderColor: "border-blue-200", + iconColor: "text-blue-500", + hoverCardBg: "hover:bg-blue-50", + hoverBorderColor: "hover:border-blue-400", +}; + +const green: CardColorScheme = { + cardBg: "bg-green-50", + iconBg: "bg-green-100", + borderColor: "border-green-200", + iconColor: "text-green-500", + hoverCardBg: "hover:bg-green-50", + hoverBorderColor: "hover:border-green-400", +}; + +const purple: CardColorScheme = { + cardBg: "bg-purple-50", + iconBg: "bg-purple-100", + borderColor: "border-purple-200", + iconColor: "text-purple-500", + hoverCardBg: "hover:bg-purple-50", + hoverBorderColor: "hover:border-purple-400", +}; + +const orange: CardColorScheme = { + cardBg: "bg-orange-50", + iconBg: "bg-orange-100", + borderColor: "border-orange-200", + iconColor: "text-orange-500", + hoverCardBg: "hover:bg-orange-50", + hoverBorderColor: "hover:border-orange-400", +}; + +const red: CardColorScheme = { + cardBg: "bg-red-50", + iconBg: "bg-red-100", + borderColor: "border-red-200", + iconColor: "text-red-500", + hoverCardBg: "hover:bg-red-50", + hoverBorderColor: "hover:border-red-400", +}; + +export const cardColorSchemes: Record = { + blue, + green, + purple, + orange, + red, +} as const; diff --git a/apps/client/src/pages/home/sections/ActionCards.tsx b/apps/client/src/pages/home/sections/ActionCards.tsx new file mode 100644 index 0000000..7b5918d --- /dev/null +++ b/apps/client/src/pages/home/sections/ActionCards.tsx @@ -0,0 +1,121 @@ +import { useState } from "react"; +import { Users, Hash } from "lucide-react"; +import { ActionCard } from "../cards/ActionCard"; +import { RoomCodeInput, ROOM_CODE_LENGTH } from "../components/RoomCodeInput"; +import { Button } from "@/shared/ui/button"; +import { createQuickRoom, joinRoom } from "@/shared/api/room"; + +interface ErrorMessageProps { + message: string; +} + +function ErrorMessage({ message }: ErrorMessageProps) { + return ( +
+ {message && ( +

+ {message} +

+ )} +
+ ); +} + +export function ActionCards() { + const [roomCode, setRoomCode] = useState( + Array(ROOM_CODE_LENGTH).fill("") + ); + const [quickStartError, setQuickStartError] = useState(""); + const [joinRoomError, setJoinRoomError] = useState(""); + const [isQuickStartLoading, setIsQuickStartLoading] = useState(false); + const [isJoinRoomLoading, setIsJoinRoomLoading] = useState(false); + + const handleQuickStart = async () => { + if (isQuickStartLoading) return; + + setIsQuickStartLoading(true); + setQuickStartError(""); + + try { + const { roomCode, myPtId } = await createQuickRoom(); + + // Save PT ID first + const key = `ptId:${roomCode}`; + localStorage.setItem(key, myPtId); + + // Then join the room + await joinRoom(roomCode); + } catch (e) { + const error = e as Error; + setQuickStartError(error.message); + } finally { + setIsQuickStartLoading(false); + } + }; + + const handleJoinRoom = async () => { + const code = roomCode.join(""); + if (code.length !== ROOM_CODE_LENGTH) return; + if (isJoinRoomLoading) return; + + setIsJoinRoomLoading(true); + setJoinRoomError(""); + + try { + await joinRoom(code); + } catch (e) { + const error = e as Error; + setJoinRoomError(error.message); + } finally { + setIsJoinRoomLoading(false); + } + }; + + return ( +
+
+ +
+ + +
+
+ + +
+ + + +
+
+
+
+ ); +} diff --git a/apps/client/src/pages/home/sections/FeatureCards.tsx b/apps/client/src/pages/home/sections/FeatureCards.tsx new file mode 100644 index 0000000..956bbb6 --- /dev/null +++ b/apps/client/src/pages/home/sections/FeatureCards.tsx @@ -0,0 +1,33 @@ +import { CodeXml, Zap, UserCog } from "lucide-react"; +import { FeatureCard } from "../cards/FeatureCard"; + +export function FeatureCards() { + return ( +
+

+ Features +

+ +
+ + + +
+
+ ); +} diff --git a/apps/client/src/pages/home/sections/Hero.tsx b/apps/client/src/pages/home/sections/Hero.tsx new file mode 100644 index 0000000..31248b5 --- /dev/null +++ b/apps/client/src/pages/home/sections/Hero.tsx @@ -0,0 +1,20 @@ +import logoAnimation from "@/assets/logo_animation.svg"; + +export function Hero() { + return ( +
+
+ CodeJam Logo +
+

+ Code + Jam +

+
+
+

+ 로그인 없이 바로 시작하는 실시간 협업 코드 에디터 +

+
+ ); +} diff --git a/apps/client/src/shared/api/room.ts b/apps/client/src/shared/api/room.ts new file mode 100644 index 0000000..4be54aa --- /dev/null +++ b/apps/client/src/shared/api/room.ts @@ -0,0 +1,54 @@ +// Room REST API +// TODO: Error message mapping + +const ROOM_API_PREFIX = "/api/rooms"; + +interface CreateQuickRoomResponse { + roomCode: string; + myPtId: string; +} + +export async function checkRoomExists(roomCode: string): Promise { + try { + const response = await fetch(`${ROOM_API_PREFIX}/${roomCode}/exists`); + const { exists } = await response.json(); + return exists; + } catch (e) { + const error = e as Error; + throw error; + } +} + +export async function createQuickRoom(): Promise { + try { + const response = await fetch(`${ROOM_API_PREFIX}/quick`, { + method: "POST", + }); + + if (!response.ok) { + const message = "Failed to create quick room"; + throw new Error(message); + } + + return await response.json(); + } catch (e) { + const error = e as Error; + throw error; + } +} + +export async function joinRoom(roomCode: string): Promise { + try { + const response = await fetch(`${ROOM_API_PREFIX}/${roomCode}/join`, { + method: "POST", + }); + + if (!response.ok) { + const message = "Failed to join room"; + throw new Error(message); + } + } catch (e) { + const error = e as Error; + throw error; + } +} diff --git a/apps/client/src/shared/ui/card.tsx b/apps/client/src/shared/ui/card.tsx new file mode 100644 index 0000000..a103bc9 --- /dev/null +++ b/apps/client/src/shared/ui/card.tsx @@ -0,0 +1,75 @@ +import * as React from "react"; + +import { cn } from "@/shared/lib/utils"; + +function Card({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function CardTitle({ className, ...props }: React.ComponentProps<"h3">) { + return ( +

+ ); +} + +function CardDescription({ className, ...props }: React.ComponentProps<"p">) { + return ( +

+ ); +} + +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +

+ ); +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent, +}; diff --git a/apps/server/src/modules/room/room.controller.ts b/apps/server/src/modules/room/room.controller.ts index 4854dbf..f7a56d7 100644 --- a/apps/server/src/modules/room/room.controller.ts +++ b/apps/server/src/modules/room/room.controller.ts @@ -2,13 +2,14 @@ import { Controller, Get, Param, - NotFoundException, Post, + Redirect, + NotFoundException, } from '@nestjs/common'; import { RoomService } from './room.service'; import { CreateRoomResponseDto } from './dto/create-room-response.dto'; -@Controller('api/room') +@Controller('api/rooms') export class RoomController { constructor(private readonly roomService: RoomService) {} @@ -21,6 +22,16 @@ export class RoomController { return { exists: true }; } + @Get(':roomCode/join') + @Redirect() + async redirectToRoom(@Param('roomCode') roomCode: string) { + const exists = await this.roomService.roomExists(roomCode); + if (!exists) throw new NotFoundException(); + + const redirectUrl = `/rooms/${roomCode}`; + return { url: redirectUrl }; + } + @Post('quick') async createQuickRoom(): Promise { return await this.roomService.createQuickRoom(); diff --git a/apps/server/src/modules/room/room.service.ts b/apps/server/src/modules/room/room.service.ts index b2e4d96..6f764a4 100644 --- a/apps/server/src/modules/room/room.service.ts +++ b/apps/server/src/modules/room/room.service.ts @@ -114,9 +114,19 @@ export class RoomService { } protected generateRoomCode(roomCodeLength = 6): string { - const alphabet = - '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; const nanoid = customAlphabet(alphabet, roomCodeLength); return nanoid(); } + + /** + * Room code로 Room ID 조회 + */ + async findRoomIdByCode(roomCode: string): Promise { + const room = await this.roomRepository.findOne({ + where: { roomCode }, + select: ['roomId'], + }); + return room?.roomId ?? null; + } } diff --git a/docs/layout.md b/docs/layout.md new file mode 100644 index 0000000..630d608 --- /dev/null +++ b/docs/layout.md @@ -0,0 +1,766 @@ +## Layout Variations + +### Horizontal Split Layout + +``` ++---------------------------------------------------------+ +| [ Logo ] | +| CodeJam | +| Real-time Collaborative Code Editor | +|---------------------------------------------------------| +| | +| +------------------------+ +---------------------+ | +| | | | | | +| | [ CREATE ROOM ] | | [ Join Room ] | | +| | | | | | +| | +----------------+ | | Enter code | | +| | | ⚡︎︎︎ QUICK START | | | [X][Y][Z][1][2][3] | | +| | +----------------+ | | | | +| | | | [ Join → ] | | +| | > Advanced Settings | | | | +| | | | | | +| +------------------------+ +---------------------+ | +| | +|---------------------------------------------------------| +| +-------------+ +-------------+ +-------------+ | +| | ⚡︎︎︎ | | | | ☼︎ | | +| | ... | | ... | | ... | | +| +-------------+ +-------------+ +-------------+ | ++---------------------------------------------------------+ +``` + +### Vertical Layout + +``` ++---------------------------------------------------------+ +| [ Logo ] | +| CodeJam | +| Real-time Collaborative Code Editor | +|---------------------------------------------------------| +| | +| +-------------------------------------------------+ | +| | | | +| | [ CREATE ROOM ] | | +| | | | +| | +-----------------------------------------+ | | +| | | ⚡︎︎︎ QUICK START | | | +| | +-----------------------------------------+ | | +| | | | +| | > Advanced Settings | | +| | | | +| +-------------------------------------------------+ | +| | +| --- OR --- | +| | +| +-------------------------------------------------+ | +| | | | +| | [ Join Existing ROOM ] | | +| | (Enter your code to participate) | | +| | | | +| | [X] [Y] [Z] [1] [2] [3] | | +| | [ Join Room → ] | | +| | | | +| +-------------------------------------------------+ | +|---------------------------------------------------------| +| | +| +-------------+ +---------------+ +-------------+ | +| | ⚡︎︎︎ | | | | ☼︎ | | +| | ... | | ... | | ... | | +| +-------------+ +---------------+ +-------------+ | +| | ++---------------------------------------------------------+ +``` + +### Tab Layout + +``` ++---------------------------------------------------------+ +| [ Logo ] | +| CodeJam | +| Real-time Collaborative Code Editor | +|---------------------------------------------------------| +| | +| [ Create Room ] [ Join Room ] | +| ━━━━━━━━━━━━━━━ ───────────── | +| | +| +-------------------------------------------------+ | +| | | | +| | [ CREATE ROOM ] | | +| | | | +| | +-----------------------------------------+ | | +| | | ⚡︎︎︎ QUICK START | | | +| | +-----------------------------------------+ | | +| | | | +| | > Advanced Settings | | +| | | | +| +-------------------------------------------------+ | +| | +|---------------------------------------------------------| +| | +| +-------------+ +---------------+ +-------------+ | +| | ⚡︎︎︎ | | | | ☼︎ | | +| | ... | | ... | | ... | | +| +-------------+ +---------------+ +-------------+ | +| | ++---------------------------------------------------------+ + +``` + +### Card Grid Layout + +``` ++---------------------------------------------------------+ +| [ Logo ] | +| CodeJam | +| Real-time Collaborative Code Editor | +|---------------------------------------------------------| +| | +| +---------------------+ +---------------------+ | +| | | | | | +| | ⚡︎︎︎ Quick Start | | # Join Room | | +| | | | | | +| | Create instantly | | [X][Y][Z][1][2][3] | | +| | | | | | +| | [ Start → ] | | [ Join → ] | | +| | | | | | +| +---------------------+ +---------------------+ | +| | +| +---------------------+ | +| | | | +| | ☼︎ Custom Setup | | +| | | | +| | Advanced options | | +| | | | +| | [ Configure → ] | | +| | | | +| +---------------------+ | +| | +|---------------------------------------------------------| +| | +| +-------------+ +---------------+ +-------------+ | +| | ⚡︎︎︎ | | | | ☼︎ | | +| | ... | | ... | | ... | | +| +-------------+ +---------------+ +-------------+ | +| | ++---------------------------------------------------------+ +``` + +### Minimal Center Layout + +``` ++---------------------------------------------------------+ +| | +| [logo] | +| CodeJam | +| Real-time Collaborative Code Editor | +| | +| | +| +-------------------------------+ | +| | [ ⚡︎︎︎ Quick Start ] | | +| +-------------------------------+ | +| | +| +-------------------------------+ | +| | [X] [Y] [Z] [1] [2] [3] | | +| | [ Join Room → ] | | +| +-------------------------------+ | +| | +| > Advanced Settings | +| | +| | +|---------------------------------------------------------| +| ⚡︎︎︎ Real-time Multi-language ☼︎ Permissions | ++---------------------------------------------------------+ +``` + +### Compact Inline Layout + +``` ++---------------------------------------------------------+ +| CodeJam | +| Real-time Collaborative Code Editor | +|---------------------------------------------------------| +| | +| [ ⚡︎︎︎ Quick Start ] or [X][Y][Z][1][2][3] [ Join ] | +| | +| > Advanced room settings | +| | +|---------------------------------------------------------| +| | +| ⚡︎︎︎ Real-time • Multi-language • ☼︎ Access | +| | ++---------------------------------------------------------+ +``` + +--- + +## Feature Section Variations + +### Card Style + +``` ++---------------------------------------------------------+ +| +---------------+ +---------------+ +-------------+ | +| | ⚡︎︎︎ | | | | ☼︎ | | +| | ... | | ... | | ... | | +| +---------------+ +---------------+ +-------------+ | ++---------------------------------------------------------+ +``` + +### Icon Grid + +``` ++---------------------------------------------------------+ +| | +| ⚡︎︎︎ ☼︎ | +| | +| Real-time Multi-language Permissions | +| Sync Support Control | +| | ++---------------------------------------------------------+ +``` + +### Icon grid with dividers + +``` ++---------------------------------------------------------+ +| ⚡︎︎︎ │ │ ☼︎ | +| ... │ ... │ ... | ++---------------------------------------------------------+ +``` + +### Tab Style + +``` ++---------------------------------------------------------+ +| | +| [ Real-time Sync ] [ Multi-language ] [ Access ] | +| ━━━━━━━━━━━━━━━━━ | +| | +| ⚡︎︎︎ Real-time Synchronization | +| | +| Collaborate seamlessly with your team. See every | +| change instantly as your teammates type. No delays, | +| no conflicts, just pure productivity. | +| | +| • Zero-latency updates | +| • Automatic conflict resolution | +| • Live cursor tracking | +| | ++---------------------------------------------------------+ +``` + +### Vertical List + +``` ++---------------------------------------------------------+ +| ⚡︎︎︎ Real-time Sync | +| Multi-language Support | +| ☼︎ Permission Control | ++---------------------------------------------------------+ +``` + +### Accordion Style + +``` ++---------------------------------------------------------+ +| ▶ Real-time Sync | +| ⯆ Multi-language Support | +| Python, JavaScript, Go, Rust, TypeScript... | +| ▶ Permission Control | +| | ++---------------------------------------------------------+ +``` + +### Inline List + +``` ++--------------------------------------------------------------+ +| ⚡︎︎︎ Real-time Sync • Multi-language • ☼︎ Permission Control | ++--------------------------------------------------------------+ +``` + +### Icon + Text Inline + +``` ++-----------------------------------------------------------------------+ +| | +| ⚡︎︎︎ Real-time Sync Multi-language ☼︎ Permissions Control | +| Fast collaboration C, Python, JS, ... Host, Editor, Viewer | +| | ++-----------------------------------------------------------------------+ +``` + +### Timeline Style + +``` ++---------------------------------------------------------+ +| | +| ⚡︎︎︎ ───────────────────────────── | +| Real-time synchronization | +| Instant updates with no lag | +| | +| ──────────────────────────── | +| Multi-language support | +| 20+ languages with syntax highlighting | +| | +| ☼︎ ───────────────────────────── | +| Permission control | +| Granular role-based access | +| | ++---------------------------------------------------------+ +``` + +--- + +## Create Room Card Variations + +### Expandable Section + +``` ++-------------------------------------------------+ +| | +| [ CREATE ROOM ] | +| | +| +-----------------------------------------+ | +| | ⚡︎︎︎ QUICK START | | +| | Create a room instantly | | +| +-----------------------------------------+ | +| | +| > Advanced Settings | +| (Expand when clicked) | +| | ++-------------------------------------------------+ + +// When expanded: ++-------------------------------------------------+ +| | +| [ CREATE ROOM ] | +| | +| +-----------------------------------------+ | +| | ⚡︎︎︎ QUICK START | | +| +-----------------------------------------+ | +| | +| ∨ Advanced Settings | +| ┌─────────────────────────────────────────┐ | +| │ Text: [___________] │ | +| │ Radio: ( ) Public (•) Private │ | +| │ Dropdown: [JavaScript ▾] │ | +| │ [Create] │ | +| └─────────────────────────────────────────┘ | +| | ++-------------------------------------------------+ +``` + +### Secondary Button + Modal + +``` ++-------------------------------------------------+ +| | +| [ CREATE ROOM ] | +| | +| +-----------------------------------------+ | +| | ⚡︎︎︎ QUICK START | | +| | Create a room instantly | | +| +-----------------------------------------+ | +| | +| [ Advanced Settings... ] | +| (Click to open modal) | +| | ++-------------------------------------------------+ + +// When clicked, modal appears: ++---------------------------------------------------+ +| ╔════════════════════════════════════════════╗ | +| ║ ║ | +| ║ [ Advanced Room Settings ] [X] ║ | +| ║ ║ | +| ║ Text ║ | +| ║ [________________________________] ║ | +| ║ ║ | +| ║ Radio ║ | +| ║ ( ) Public (•) Private ║ | +| ║ ║ | +| ║ Dropdown ║ | +| ║ [ JavaScript ▾ ] ║ | +| ║ ║ | +| ║ Checkbox ║ | +| ║ [√] Auto-save ║ | +| ║ [√] Code execution ║ | +| ║ [ ] Chat ║ | +| ║ ║ | +| ║ [ Cancel ] [ Create → ] ║ | +| ║ ║ | +| ╚════════════════════════════════════════════╝ | ++---------------------------------------------------+ +``` + +### Side-by-Side Cards + +``` ++-----------------------------------------------------+ +| [ CREATE ROOM ] | +| | +| +--------------------+ +---------------------+ | +| | | | | | +| | ⚡︎︎ Quick Start | | ☼︎ Custom Setup | | +| | | | | | +| | Create instantly | | Advanced options | | +| | with defaults | | and configurations | | +| | | | | | +| | [ Start → ] | | [ Configure → ] | | +| | | | | | +| +--------------------+ +---------------------+ | +| | ++-----------------------------------------------------+ +``` + +### Stacked Cards + +``` ++-------------------------------------------------+ +| [ CREATE ROOM ] | +| | +| +----------------------------------------+ | +| | ⚡ Quick Start | | +| | | | +| | Create a room instantly with default | | +| | settings | | +| | | | +| | [ Start → ] | | +| +----------------------------------------+ | +| | +| +----------------------------------------+ | +| | ☼ Custom Setup | | +| | | | +| | Configure advanced room options and | | +| | settings | | +| | | | +| | [ Configure → ] | | +| +----------------------------------------+ | +| | ++-------------------------------------------------+ +``` + +### Tab Style + +``` ++-------------------------------------------------+ +| [ CREATE ROOM ] | +| | +| [ ⚡ Quick Start ] [ ☼ Custom Setup ] | +| ━━━━━━━━━━━━━━━━━ ────────────────── | +| | +| +----------------------------------------+ | +| | | | +| | Create a room instantly with default | | +| | settings. Perfect for quick | | +| | collaboration sessions. | | +| | | | +| | [ Start → ] | | +| | | | +| +----------------------------------------+ | +| | ++-------------------------------------------------+ +``` + +### Icon Grid + Progressive Disclosure + +#### Step 1: Initial Choice + +``` ++-------------------------------------------------+ +| [ CREATE ROOM ] | +| | +| | +| +---------------+ +---------------+ | +| | | | | | +| | ⚡ | | ☼ | | +| | | | | | +| | Quick Start | | Custom Setup | | +| | | | | | +| +---------------+ +---------------+ | +| | +| | ++-------------------------------------------------+ +``` + +#### Step 2A: Quick Start + +``` ++-------------------------------------------------+ +| [ CREATE ROOM ] | +| ⚡ Quick Start Selected | +| | +| Creating room with default settings... | +| | +| ████████████████░░░░░░░░░░ 60% | +| | +| → Redirecting to room... | +| | ++-------------------------------------------------+ +``` + +#### Step 2B: Custom Setup → Progressive Form + +``` ++-------------------------------------------------+ +| [ CREATE ROOM ] | +| ☼ Custom Setup Selected | +| | +| Text | +| [__________] | +| | +| Radio | +| ( ) Public (•) Private | +| | +| Dropdown | +| [ JavaScript ▾ ] | +| | +| Checkbox | +| [√] Auto-save | +| [√] Code execution | +| [ ] Chat | +| | +| [ Back ] [ Create ] | +| | ++-------------------------------------------------+ +``` + +--- + +## Room Code Input Field Variations + +### Brackets + +``` ++-------------------------------------------------+ +| [ X ] [ Y ] [ Z ] [ 1 ] [ 2 ] [ 3 ] | ++-------------------------------------------------+ +``` + +### Box (Monospace) + +``` ++-------------------------------------------------+ +| | +| ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ | +| │ X │ │ Y │ │ Z │ │ 1 │ │ 2 │ │ 3 │ | +| └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ | +| | ++-------------------------------------------------+ +``` + +### Double-lined Boxes + +``` ++-------------------------------------------------+ +| | +| ╔═══╗ ╔═══╗ ╔═══╗ ╔═══╗ ╔═══╗ ╔═══╗ | +| ║ X ║ ║ Y ║ ║ Z ║ ║ 1 ║ ║ 2 ║ ║ 3 ║ | +| ╚═══╝ ╚═══╝ ╚═══╝ ╚═══╝ ╚═══╝ ╚═══╝ | +| | ++-------------------------------------------------+ +``` + +### Underline Style (Minimal) + +``` ++-------------------------------------------------+ +| | +| X Y Z 1 2 3 | +| ─── ─── ─── ─── ─── ─── | +| | ++-------------------------------------------------+ +``` + +### Single Input with Wide Letter Spacing + +``` ++-------------------------------------------------+ +| | +| ┌───────────────────────────────────┐ | +| │ X Y Z 1 2 3 │ | +| └───────────────────────────────────┘ | +| (letter-spacing: 2em or wider) | +| | ++-------------------------------------------------+ + +// With cursor: ++-------------------------------------------------+ +| | +| ┌───────────────────────────────────┐ | +| │ X Y Z 1 2 3 | │ | +| └───────────────────────────────────┘ | +| | ++-------------------------------------------------+ + +// Empty state: ++-------------------------------------------------+ +| ┌───────────────────────────────────┐ | +| │ _ _ _ _ _ _ │ | +| └───────────────────────────────────┘ | ++-------------------------------------------------+ +``` + +--- + +## Focus Indicator Variations + +### Underline Blinking + +``` +Frame 1 (0.0s): +┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ +│ X │ │ Y │ │ _ │ │ _ │ │ _ │ │ _ │ +└───┘ └───┘ └─█─┘ └───┘ └───┘ └───┘ + ↑ + (blinking) + +Frame 2 (0.5s): +┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ +│ X │ │ Y │ │ _ │ │ _ │ │ _ │ │ _ │ +└───┘ └───┘ └─▁─┘ └───┘ └───┘ └───┘ + ↑ + (blinking) +``` + +### Vertical Cursor Blinking + +``` +Frame 1 (0.0s): +┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ +│ X │ │ Y │ │|_ │ │ _ │ │ _ │ │ _ │ +└───┘ └───┘ └───┘ └───┘ └───┘ └───┘ + ↑ + (currently typing) + +Frame 2 (0.5s): +┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ +│ X │ │ Y │ │ _ │ │ _ │ │ _ │ │ _ │ +└───┘ └───┘ └───┘ └───┘ └───┘ └───┘ + ↑ + (cursor hidden) +``` + +### Bold Border + +``` +┌───┐ ┌───┐ ╔═══╗ ┌───┐ ┌───┐ ┌───┐ +│ X │ │ Y │ ║ _ ║ │ _ │ │ _ │ │ _ │ +└───┘ └───┘ ╚═══╝ └───┘ └───┘ └───┘ + ↑ + (active focus) +``` + +### Color Border + +``` +┌───┐ ┌───┐ ┏━━━┓ ┌───┐ ┌───┐ ┌───┐ +│ X │ │ Y │ ┃ _ ┃ │ _ │ │ _ │ │ _ │ +└───┘ └───┘ ┗━━━┛ └───┘ └───┘ └───┘ + ↑ + (blue/green border) +``` + +### Shadow/Glow Effect + +``` + ▒▒▒▒▒▒▒ +┌───┐ ┌───┐▒┌───┐▒┌───┐ ┌───┐ ┌───┐ +│ X │ │ Y │▒│ _ │▒│ _ │ │ _ │ │ _ │ +└───┘ └───┘▒└───┘▒└───┘ └───┘ └───┘ + ▒▒▒▒▒▒▒ + ↑ + (shadow/glow) +``` + +### Background Fill + +``` +┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ +│ X │ │ Y │ │▓_▓│ │ _ │ │ _ │ │ _ │ +└───┘ └───┘ └───┘ └───┘ └───┘ └───┘ + ↑ + (background filled) +``` + +### Arrow Indicator + +``` +┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ +│ X │ │ Y │ │ _ │ │ _ │ │ _ │ │ _ │ +└───┘ └───┘ └─▲─┘ └───┘ └───┘ └───┘ + │ + (currently typing) +``` + +### Top Bar Indicator + +``` + ⯆ +┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ +│ X │ │ Y │ │ _ │ │ _ │ │ _ │ │ _ │ +└───┘ └───┘ └───┘ └───┘ └───┘ └───┘ + ↑ + (top indicator) +``` + +### Multiple Indicators Combined + +``` +Frame 1: +┌───┐ ┌───┐ ╔═══╗ ┌───┐ ┌───┐ ┌───┐ +│ X │ │ Y │ ║|_ ║ │ _ │ │ _ │ │ _ │ +└───┘ └───┘ ╚═▲═╝ └───┘ └───┘ └───┘ + │ + (bold border + cursor + arrow) + +Frame 2: +┌───┐ ┌───┐ ╔═══╗ ┌───┐ ┌───┐ ┌───┐ +│ X │ │ Y │ ║ _ ║ │ _ │ │ _ │ │ _ │ +└───┘ └───┘ ╚═▲═╝ └───┘ └───┘ └───┘ + │ + (cursor hidden) +``` + +--- + +## Hero Section Variations + +### Center + +``` ++---------------------------------------------------------+ +| [ Logo ] | +| CodeJam | +| Real-time Collaborative Code Editor | ++---------------------------------------------------------+ +``` + +### Left Aligned + +``` ++---------------------------------------------------------+ +| [ Logo ] | +| CodeJam | +| Real-time Collaborative Code Editor | ++---------------------------------------------------------+ +``` + +### Minimal Inline + +``` ++---------------------------------------------------------+ +| [ Logo ] CodeJam • Real-time Collaborative Editor | ++---------------------------------------------------------+ +``` + +### Navigation + +``` ++---------------------------------------------------------+ +| [ Logo ] CodeJam Home Features Docs Login | +| | +| Real-time Collaborative Code Editor | ++---------------------------------------------------------+ +```