Fix [hidden] being overridden by button styles, render Google/Kakao/Naver quick login as icons, and show logout only when authenticated. Co-authored-by: Cursor <cursoragent@cursor.com>
366 lines
14 KiB
HTML
366 lines
14 KiB
HTML
<!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" />
|
||
<!-- Auth0 SPA SDK (정적 사이트용) -->
|
||
<!-- jsDelivr 차단/실패 시 unpkg로 자동 대체 -->
|
||
<script
|
||
src="https://cdn.jsdelivr.net/npm/@auth0/auth0-spa-js@2/dist/auth0-spa-js.production.js"
|
||
onerror="this.onerror=null;this.src='https://unpkg.com/@auth0/auth0-spa-js@2/dist/auth0-spa-js.production.js';"
|
||
></script>
|
||
<script>
|
||
// 로그인 설정 (관리 기능 잠금용)
|
||
// 1) Auth0에서 Application(SPA) 생성 후 domain/clientId를 입력하세요.
|
||
// 2) Allowed Callback URLs / Allowed Logout URLs에 현재 사이트 주소를 등록하세요.
|
||
// 예: https://drive.daewoongai.com/apps/dashboard/
|
||
window.AUTH_CONFIG = {
|
||
auth0: {
|
||
domain: "",
|
||
clientId: "",
|
||
},
|
||
// 관리 허용 이메일(대소문자 무시)
|
||
allowedEmails: [],
|
||
};
|
||
</script>
|
||
<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 class="user" id="user" hidden>
|
||
<span class="user-dot" aria-hidden="true"></span>
|
||
<span class="user-text" id="userText">로그인 필요</span>
|
||
</div>
|
||
<button class="btn icon-only provider-google" id="btnGoogle" type="button" hidden aria-label="구글로 로그인">
|
||
<span class="sr-only">구글</span>
|
||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||
<path
|
||
fill="#EA4335"
|
||
d="M12 10.2v3.9h5.45c-.24 1.26-1.44 3.7-5.45 3.7-3.28 0-5.96-2.72-5.96-6.1S8.72 5.6 12 5.6c1.87 0 3.12.8 3.84 1.5l2.62-2.52C16.86 3.08 14.7 2 12 2 6.48 2 2 6.48 2 11.7S6.48 21.4 12 21.4c6.92 0 9.6-4.86 9.6-7.38 0-.5-.06-.88-.13-1.26H12z"
|
||
/>
|
||
<path
|
||
fill="#34A853"
|
||
d="M3.15 7.55l3.21 2.36C7.2 7.7 9.38 5.6 12 5.6c1.87 0 3.12.8 3.84 1.5l2.62-2.52C16.86 3.08 14.7 2 12 2 8.16 2 4.84 4.1 3.15 7.55z"
|
||
opacity=".0"
|
||
/>
|
||
</svg>
|
||
</button>
|
||
<button class="btn icon-only provider-kakao" id="btnKakao" type="button" hidden aria-label="카카오로 로그인">
|
||
<span class="sr-only">카카오</span>
|
||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||
<path
|
||
fill="#FAE100"
|
||
d="M12 3C6.76 3 2.5 6.23 2.5 10.22c0 2.58 1.78 4.85 4.45 6.12l-1.01 3.69a.7.7 0 0 0 1.05.77l4.3-2.85c.55.05 1.11.08 1.66.08 5.24 0 9.5-3.23 9.5-7.22C21.5 6.23 17.24 3 12 3z"
|
||
/>
|
||
<path
|
||
fill="#000"
|
||
d="M12 6.4c-3.7 0-6.7 2.2-6.7 4.9S8.3 16.2 12 16.2s6.7-2.2 6.7-4.9-3-4.9-6.7-4.9z"
|
||
opacity=".08"
|
||
/>
|
||
</svg>
|
||
</button>
|
||
<button class="btn icon-only provider-naver" id="btnNaver" type="button" hidden aria-label="네이버로 로그인">
|
||
<span class="sr-only">네이버</span>
|
||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||
<path fill="#03C75A" d="M6 4h5.1l6.9 10.1V4H18v16h-5.1L6 9.9V20H6V4z" />
|
||
</svg>
|
||
</button>
|
||
<button class="btn" id="btnLogin" type="button">로그인</button>
|
||
<button class="btn" id="btnLogout" type="button" hidden>로그아웃</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>
|
||
|
||
<!-- Auth Config Modal -->
|
||
<div class="modal" id="authModal" role="dialog" aria-modal="true" aria-labelledby="authModalTitle" hidden>
|
||
<div class="modal-backdrop" data-auth-close="1"></div>
|
||
<div class="modal-card" role="document">
|
||
<div class="modal-head">
|
||
<div class="modal-title" id="authModalTitle">로그인 설정</div>
|
||
<button class="icon-btn" type="button" id="btnAuthClose" title="닫기" aria-label="닫기">×</button>
|
||
</div>
|
||
|
||
<form id="authForm" class="modal-body">
|
||
<label class="field">
|
||
<span class="field-label">Auth0 Domain</span>
|
||
<input id="authDomain" class="input" type="text" placeholder="예: your-tenant.us.auth0.com" />
|
||
<div class="hint">Auth0 테넌트 도메인입니다. (비밀값 아님)</div>
|
||
</label>
|
||
|
||
<label class="field">
|
||
<span class="field-label">Auth0 Client ID</span>
|
||
<input id="authClientId" class="input" type="text" placeholder="예: AbCdEf..." />
|
||
<div class="hint">Auth0 SPA Application의 Client ID입니다. (비밀값 아님)</div>
|
||
</label>
|
||
|
||
<label class="field">
|
||
<span class="field-label">허용 이메일</span>
|
||
<input id="authAllowedEmails" class="input" type="text" placeholder="예: me@example.com, admin@example.com" />
|
||
<div class="hint">쉼표로 구분합니다. 비워두면 “로그인한 모든 계정”이 관리 가능해집니다.</div>
|
||
</label>
|
||
|
||
<label class="field">
|
||
<span class="field-label">Connection 이름(선택)</span>
|
||
<input id="authConnGoogle" class="input" type="text" placeholder="Google 예: google-oauth2" />
|
||
<input id="authConnKakao" class="input" type="text" placeholder="Kakao 예: kakao" />
|
||
<input id="authConnNaver" class="input" type="text" placeholder="Naver 예: naver" />
|
||
<div class="hint">Auth0에서 설정한 connection 이름입니다. 비우면 “로그인” 버튼으로 통합 로그인 화면을 띄웁니다.</div>
|
||
</label>
|
||
|
||
<div class="modal-foot">
|
||
<button class="btn btn-ghost" type="button" id="btnAuthReset">초기화</button>
|
||
<button class="btn btn-primary" type="submit" id="btnAuthSave">저장</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": "tts-ncue-net",
|
||
"title": "TTS",
|
||
"url": "https://tts.ncue.net/",
|
||
"description": "입력한 text를 mp3로 변환",
|
||
"tags": ["text", "mp3", "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>
|