Add static links dashboard

Includes JSON-ordered link cards, search/sort, favorites, CRUD, and import/export with localStorage.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dsyoon
2026-02-07 17:33:14 +09:00
commit 02082eb16d
5 changed files with 1596 additions and 0 deletions

251
index.html Normal file
View File

@@ -0,0 +1,251 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="light dark" />
<title>NCue | 개인 링크 홈</title>
<meta name="description" content="개인 서비스 링크를 모아 관리하는 홈 화면" />
<link rel="stylesheet" href="./styles.css" />
<script defer src="./script.js"></script>
</head>
<body>
<a class="skip-link" href="#main">본문으로 건너뛰기</a>
<header class="topbar">
<div class="wrap">
<div class="brand">
<div class="logo" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none">
<path
d="M10.5 13.5l3-3m-1.24-3.13l-2.86-2.86a4 4 0 0 0-5.66 5.66l2.86 2.86"
stroke="currentColor"
stroke-width="1.8"
stroke-linecap="round"
/>
<path
d="M13.5 10.5l2.86 2.86a4 4 0 0 1-5.66 5.66l-2.86-2.86"
stroke="currentColor"
stroke-width="1.8"
stroke-linecap="round"
/>
</svg>
</div>
<div class="brand-text">
<div class="brand-title">NCue</div>
<div class="brand-sub" id="subtitle">개인 링크 관리</div>
</div>
</div>
<div class="actions">
<button class="btn" id="btnAdd" type="button">
<span class="btn-ico" aria-hidden="true">+</span>
추가
</button>
<button class="btn" id="btnImport" type="button">가져오기</button>
<button class="btn" id="btnExport" type="button">내보내기</button>
<button class="btn" id="btnTheme" type="button" aria-pressed="false" title="테마 전환">
테마
</button>
</div>
</div>
</header>
<main class="wrap" id="main">
<section class="panel">
<div class="controls">
<label class="field">
<span class="field-label">검색</span>
<input
id="q"
class="input"
type="search"
placeholder="제목/도메인/태그 검색…"
autocomplete="off"
spellcheck="false"
/>
</label>
<label class="field">
<span class="field-label">정렬</span>
<select id="sort" class="select">
<option value="json">파일 순서</option>
<option value="recent">최근 수정</option>
<option value="name">이름</option>
<option value="domain">도메인</option>
<option value="favorite">즐겨찾기 우선</option>
</select>
</label>
<label class="check">
<input id="onlyFav" type="checkbox" />
<span>즐겨찾기만</span>
</label>
<div class="meta" id="meta" aria-live="polite"></div>
</div>
</section>
<section class="grid" id="grid" aria-label="링크 목록"></section>
<section class="empty" id="empty" hidden>
<div class="empty-title">표시할 링크가 없습니다.</div>
<div class="empty-sub">상단의 “추가” 버튼으로 새 링크를 등록하세요.</div>
</section>
</main>
<!-- Modal -->
<div class="modal" id="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle" hidden>
<div class="modal-backdrop" data-close="1"></div>
<div class="modal-card" role="document">
<div class="modal-head">
<div class="modal-title" id="modalTitle">링크 추가</div>
<button class="icon-btn" type="button" id="btnClose" title="닫기" aria-label="닫기">×</button>
</div>
<form id="form" class="modal-body">
<input type="hidden" id="id" />
<label class="field">
<span class="field-label">제목</span>
<input id="title" class="input" type="text" required maxlength="80" placeholder="예: Git" />
</label>
<label class="field">
<span class="field-label">URL</span>
<input
id="url"
class="input"
type="url"
required
placeholder="예: https://example.com"
inputmode="url"
autocomplete="off"
spellcheck="false"
/>
<div class="hint">http(s) URL을 권장합니다. (미입력 시 자동으로 https://가 붙습니다)</div>
</label>
<label class="field">
<span class="field-label">설명</span>
<input id="description" class="input" type="text" maxlength="140" placeholder="선택" />
</label>
<label class="field">
<span class="field-label">태그</span>
<input id="tags" class="input" type="text" placeholder="예: ncue, dev (쉼표로 구분)" />
</label>
<label class="check">
<input id="favorite" type="checkbox" />
<span>즐겨찾기</span>
</label>
<div class="modal-foot">
<button class="btn btn-ghost" type="button" id="btnCancel">취소</button>
<button class="btn btn-primary" type="submit" id="btnSave">저장</button>
</div>
</form>
</div>
</div>
<!-- Hidden file input for import -->
<input id="file" type="file" accept="application/json" hidden />
<!-- Toast -->
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
<!--
기본 링크 데이터(서버 없이 index.html만 열어도 동작)
이 배열 순서가 "파일 순서" 정렬 기준이 됩니다.
-->
<script id="linksData" type="application/json">
[
{
"id": "dsyoon-ncue-net",
"title": "DSYoon",
"url": "https://ncue.net/dsyoon",
"description": "개인 페이지",
"tags": ["personal", "ncue"],
"favorite": false,
"createdAt": "2026-02-07T00:00:00.000Z",
"updatedAt": "2026-02-07T00:00:00.000Z"
},
{
"id": "family-ncue-net",
"title": "Family",
"url": "https://ncue.net/family",
"description": "Family",
"tags": ["personal", "ncue"],
"favorite": false,
"createdAt": "2026-02-07T00:00:00.000Z",
"updatedAt": "2026-02-07T00:00:00.000Z"
},
{
"id": "git-ncue-net",
"title": "Git",
"url": "https://git.ncue.net/",
"description": "NCUE Git 서비스",
"tags": ["dev", "ncue"],
"favorite": false,
"createdAt": "2026-02-07T00:00:00.000Z",
"updatedAt": "2026-02-07T00:00:00.000Z"
},
{
"id": "mail-ncue-net",
"title": "Mail",
"url": "https://mail.ncue.net/",
"description": "NCUE 메일",
"tags": ["mail", "ncue"],
"favorite": false,
"createdAt": "2026-02-07T00:00:00.000Z",
"updatedAt": "2026-02-07T00:00:00.000Z"
},
{
"id": "meeting-ncue-net",
"title": "Meeting",
"url": "https://meeting.ncue.net/",
"description": "NCUE 미팅",
"tags": ["meeting", "ncue"],
"favorite": false,
"createdAt": "2026-02-07T00:00:00.000Z",
"updatedAt": "2026-02-07T00:00:00.000Z"
},
{
"id": "openclaw-ncue-net",
"title": "OpenClaw",
"url": "https://openclaw.ncue.net/",
"description": "OpenClaw",
"tags": ["tool", "ncue"],
"favorite": false,
"createdAt": "2026-02-07T00:00:00.000Z",
"updatedAt": "2026-02-07T00:00:00.000Z"
},
{
"id": "link-ncue-net",
"title": "Link",
"url": "https://link.ncue.net/",
"description": "NCUE 링크 허브",
"tags": ["link", "ncue"],
"favorite": false,
"createdAt": "2026-02-07T00:00:00.000Z",
"updatedAt": "2026-02-07T00:00:00.000Z"
},
{
"id": "dreamgirl-ncue-net",
"title": "DreamGirl",
"url": "https://ncue.net/dreamgirl",
"description": "DreamGirl",
"tags": ["personal", "ncue"],
"favorite": false,
"createdAt": "2026-02-07T00:00:00.000Z",
"updatedAt": "2026-02-07T00:00:00.000Z"
}
]
</script>
<noscript>
<div class="noscript">이 페이지는 JavaScript가 필요합니다.</div>
</noscript>
</body>
</html>