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 (
+
+
+

+
+
+ 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 |
++---------------------------------------------------------+
+```