-
-
- ${user.username.charAt(0).toUpperCase()}
-
-
-
${user.username}
-
GitHub ID
-
+
+
${badgesHtml}
+
`;
}).join('');
- // Attach event listeners
- document.querySelectorAll('.user-role-select').forEach(select => {
- select.addEventListener('change', handleUserRoleChange);
- });
-
- document.querySelectorAll('.delete-user-btn').forEach(btn => {
- btn.addEventListener('click', handleDeleteUser);
- });
-
- // Apply permissions to dynamically rendered elements
- if (appState.currentUser?.permissions) {
- applyPermissions();
- }
-}
-
-async function handleUserRoleChange(e) {
- const username = e.target.dataset.username;
- const newRole = e.target.value;
-
- const user = appState.users.find(u => u.username === username);
- if (user) {
- user.role = newRole;
-
- // If this is the current user, update the badge immediately
- if (username === appState.currentUser.username) {
- appState.currentUser.role = newRole;
- updateUserRoleBadge(newRole);
- }
-
- await syncUsersToRoles();
- showToast(`${username}의 역할이 ${newRole}로 변경되었습니다`);
- markDirty();
- }
-}
-
-async function handleDeleteUser(e) {
- const username = e.target.dataset.username;
-
- // Safety check: Ensure at least one policy editor remains
- const userToDelete = appState.users.find(u => u.username === username);
- if (!userToDelete) return;
-
- // Check if this user has policy edit permission
- const userRole = appState.policy.rbac?.roles?.[userToDelete.role];
- if (userRole && userRole.canEditPolicy) {
- // Count how many users with policy edit permission exist
- const policyEditors = appState.users.filter(u => {
- const role = appState.policy.rbac?.roles?.[u.role];
- return role && role.canEditPolicy;
+ // Attach click handlers
+ container.querySelectorAll('.role-select-btn').forEach(btn => {
+ btn.addEventListener('click', async (e) => {
+ const selectedRole = e.currentTarget.dataset.role;
+ if (selectedRole !== currentRole) {
+ await selectRole(selectedRole);
+ }
});
-
- // If this is the last policy editor, prevent deletion
- if (policyEditors.length === 1) {
- alert(`❌ ${username}은(는) 유일한 정책 편집자입니다.\n\n최소 한 명의 정책 편집자는 남아있어야 합니다.\n먼저 다른 사용자에게 정책 편집 권한을 부여한 후 삭제해주세요.`);
- return;
- }
- }
-
- if (!confirm(`${username}을(를) 삭제하시겠습니까?`)) return;
-
- appState.users = appState.users.filter(u => u.username !== username);
- await syncUsersToRoles();
- renderUsers();
- showToast(`${username}이(가) 삭제되었습니다`);
- markDirty();
+ });
}
-async function handleAddUser() {
- const username = prompt('새 사용자의 GitHub ID를 입력하세요:');
- if (!username || !username.trim()) return;
-
- const trimmedUsername = username.trim();
-
- if (appState.users.some(u => u.username === trimmedUsername)) {
- showToast('이미 존재하는 사용자입니다', 'error');
- return;
- }
+async function selectRole(role) {
+ try {
+ const result = await API.selectRole(role);
- appState.users.push({ username: trimmedUsername, role: 'viewer' });
- await syncUsersToRoles();
- renderUsers();
- showToast(`${trimmedUsername}이(가) 추가되었습니다`);
- markDirty();
-}
+ // Update current user state
+ appState.currentUser.role = result.role;
+ appState.currentUser.permissions = result.permissions;
-async function syncUsersToRoles() {
- const roles = { admin: [], developer: [], viewer: [] };
+ // Update header UI
+ updateUserRoleBadge(result.role);
+ updatePermissionBadges(result.permissions);
- appState.users.forEach(user => {
- if (roles[user.role]) {
- roles[user.role].push(user.username);
- }
- });
+ // Re-render all UI sections first
+ renderRoleSelection();
+ renderRBAC();
+ renderRules();
+ renderLanguageTags();
- // Update policy RBAC if needed
- // (This function updates appState.users based on roles.json)
-}
+ // Apply permissions AFTER all renders so new elements get proper state
+ applyPermissions();
-async function loadUsersFromRoles() {
- try {
- const rolesData = await API.getRoles();
- const users = [];
-
- // Load all roles dynamically, not just admin/developer/viewer
- Object.entries(rolesData).forEach(([role, usernames]) => {
- if (Array.isArray(usernames)) {
- usernames.forEach(username => {
- users.push({ username, role });
- });
- }
- });
+ showToast(`역할이 '${result.role}'(으)로 변경되었습니다`);
- appState.users = users;
- renderUsers();
} catch (error) {
- console.error('Failed to load users:', error);
- showToast('사용자 목록을 불러오는데 실패했습니다', 'error');
+ console.error('Failed to select role:', error);
+ showToast('역할 변경에 실패했습니다: ' + error.message, 'error');
}
}
@@ -782,19 +763,18 @@ function handleRBACUpdate(e) {
appState.policy.rbac.roles[newRoleName] = appState.policy.rbac.roles[oldRoleName];
delete appState.policy.rbac.roles[oldRoleName];
- // Update all users with this role
- appState.users.forEach(user => {
- if (user.role === oldRoleName) {
- user.role = newRoleName;
- }
- });
-
roleCard.dataset.roleName = newRoleName;
// Update all related elements
roleCard.querySelectorAll('[data-role-name]').forEach(el => {
el.dataset.roleName = newRoleName;
});
- renderUsers(); // Update user role dropdowns when role name changes
+
+ // Update available roles list
+ const index = appState.availableRoles.indexOf(oldRoleName);
+ if (index !== -1) {
+ appState.availableRoles[index] = newRoleName;
+ }
+ renderRoleSelection();
showToast(`역할 이름이 "${oldRoleName}"에서 "${newRoleName}"(으)로 변경되었습니다`);
}
} else {
@@ -820,19 +800,24 @@ function handleRBACUpdate(e) {
function handleDeleteRBACRole(e) {
const roleName = e.target.dataset.roleName;
- // Check if any users have this role
- const usersWithRole = appState.users.filter(u => u.role === roleName);
- if (usersWithRole.length > 0) {
- const usernames = usersWithRole.map(u => u.username).join(', ');
- alert(`❌ 이 역할을 사용 중인 사용자가 있어 삭제할 수 없습니다.\n\n사용자: ${usernames}\n\n먼저 해당 사용자들의 역할을 변경한 후 삭제해주세요.`);
+ // Check if this is the currently selected role
+ if (roleName === appState.currentUser?.role) {
+ alert(`❌ 현재 선택된 역할은 삭제할 수 없습니다.\n\n다른 역할을 선택한 후 삭제해주세요.`);
return;
}
if (!confirm(`${roleName} 역할을 삭제하시겠습니까?`)) return;
delete appState.policy.rbac.roles[roleName];
+
+ // Update available roles list
+ const index = appState.availableRoles.indexOf(roleName);
+ if (index !== -1) {
+ appState.availableRoles.splice(index, 1);
+ }
+
renderRBAC();
- renderUsers(); // Update user role dropdowns
+ renderRoleSelection();
showToast(`${roleName} 역할이 삭제되었습니다`);
markDirty();
}
@@ -1019,12 +1004,6 @@ async function savePolicy() {
}
try {
- // Sync current user role first (in case it was changed in the UI)
- const currentUserInList = appState.users.find(u => u.username === appState.currentUser.username);
- if (currentUserInList) {
- appState.currentUser.role = currentUserInList.role;
- }
-
// Validate: At least one role must have canEditPolicy permission
if (appState.policy.rbac && appState.policy.rbac.roles) {
const hasAtLeastOneEditor = Object.values(appState.policy.rbac.roles).some(role => role.canEditPolicy);
@@ -1034,25 +1013,16 @@ async function savePolicy() {
}
}
- // Check if current user is losing policy edit privileges
- const currentUser = appState.users.find(u => u.username === appState.currentUser.username);
+ // Check if current role is losing policy edit privileges
const currentRole = appState.currentUser.role;
- const newRole = currentUser?.role;
-
- if (currentRole && newRole && currentRole !== newRole) {
- // Check if current user is losing policy edit permission
- const currentRoleData = appState.policy.rbac?.roles?.[currentRole];
- const newRoleData = appState.policy.rbac?.roles?.[newRole];
-
- if (currentRoleData?.canEditPolicy && !newRoleData?.canEditPolicy) {
- const confirmLoss = confirm(
- `⚠️ 경고: 자신의 정책 편집 권한을 제거하려고 합니다.\n\n` +
- `현재 역할: ${currentRole}\n` +
- `변경될 역할: ${newRole}\n\n` +
- `정책 편집 권한을 잃으면 정책을 수정할 수 없게 됩니다.\n계속하시겠습니까?`
- );
- if (!confirmLoss) return;
- }
+ const currentRoleData = appState.policy.rbac?.roles?.[currentRole];
+
+ if (currentRoleData && !currentRoleData.canEditPolicy) {
+ const confirmLoss = confirm(
+ `⚠️ 경고: 현재 역할(${currentRole})에서 정책 편집 권한을 제거하려고 합니다.\n\n` +
+ `정책 편집 권한을 잃으면 정책을 수정할 수 없게 됩니다.\n계속하시겠습니까?`
+ );
+ if (!confirmLoss) return;
}
// Update defaults from UI
@@ -1062,28 +1032,11 @@ async function savePolicy() {
appState.policy.defaults.severity = document.getElementById('defaults-severity').value || undefined;
appState.policy.defaults.defaultLanguage = document.getElementById('defaults-default-language').value || undefined;
- // Collect roles from users
- const roles = {};
- appState.users.forEach(user => {
- if (!roles[user.role]) {
- roles[user.role] = [];
- }
- roles[user.role].push(user.username);
- });
-
- // Ensure default roles exist even if empty
- if (!roles.admin) roles.admin = [];
- if (!roles.developer) roles.developer = [];
- if (!roles.viewer) roles.viewer = [];
-
- console.log('[DEBUG] Saving roles:', roles);
- console.log('[DEBUG] Current user role:', appState.currentUser.role);
+ console.log('[DEBUG] Current role:', appState.currentUser.role);
console.log('[DEBUG] Policy RBAC roles:', Object.keys(appState.policy.rbac?.roles || {}));
- // IMPORTANT: Save policy FIRST (so RBAC roles are defined)
- // Then save roles (so permission checks can find the role in RBAC)
+ // Save policy
await API.savePolicy(appState.policy);
- await API.saveRoles(roles);
// Save policy path if changed
const newPath = document.getElementById('policy-path-input').value.trim();
@@ -1181,7 +1134,6 @@ async function loadPolicy() {
console.log('Policy loaded. Rules count:', appState.policy.rules.length);
console.log('Original rules stored:', appState.originalRules.substring(0, 100) + '...');
console.log('Original RBAC stored:', appState.originalRBAC.substring(0, 100) + '...');
- await loadUsersFromRoles();
renderAll();
showToast('정책을 불러왔습니다');
} catch (error) {
@@ -1247,23 +1199,25 @@ async function saveSettings() {
// Update user role badge display
function updateUserRoleBadge(role) {
const roleBadge = document.getElementById('user-role-badge');
+ const roleColor = getRoleColor(role);
roleBadge.textContent = role;
- roleBadge.className = `badge text-xs px-2 py-1 rounded-full ${
- role === 'admin' ? 'bg-yellow-400 text-slate-900' :
- role === 'developer' ? 'bg-blue-500 text-white' :
- 'bg-gray-400 text-white'
- }`;
+ roleBadge.className = `font-semibold text-sm px-3 py-1 rounded-full ${roleColor.bg} ${roleColor.text}`;
+}
+
+function updatePermissionBadges(permissions) {
+ const container = document.getElementById('permission-badges');
+ if (!container) return;
+ const badges = getPermissionBadges(permissions);
+ container.innerHTML = badges.map(renderPermissionBadge).join('');
}
// Update user info display in header
function updateUserInfo() {
- const currentUser = appState.users.find(u => u.username === appState.currentUser.username);
- if (currentUser) {
- // Update appState.currentUser.role
- appState.currentUser.role = currentUser.role;
-
+ // In role-based mode, just update the display with current role
+ const currentRole = appState.currentUser.role;
+ if (currentRole) {
// Update badge display
- updateUserRoleBadge(currentUser.role);
+ updateUserRoleBadge(currentRole);
}
}
@@ -1282,8 +1236,8 @@ function renderAll() {
// Rules
renderRules();
- // Users (to update role dropdowns if RBAC roles changed)
- renderUsers();
+ // Role selection
+ renderRoleSelection();
}
// ==================== Permission-Based UI ====================
@@ -1294,8 +1248,57 @@ function applyPermissions() {
console.log('[Permissions] canEditPolicy:', canEditPolicy, 'canEditRoles:', canEditRoles);
- // When canEditPolicy = false: Read-only policy UI
- if (!canEditPolicy) {
+ if (canEditPolicy) {
+ // Show save buttons
+ document.getElementById('save-btn')?.classList.remove('hidden');
+ document.getElementById('floating-save-btn')?.classList.remove('hidden');
+
+ // Show template button
+ document.getElementById('template-btn')?.classList.remove('hidden');
+
+ // Enable RBAC inputs and show add/delete buttons
+ document.querySelectorAll('.role-name-input, .role-allowWrite-input, .role-denyWrite-input').forEach(el => {
+ el.disabled = false;
+ el.classList.remove('bg-gray-200', 'cursor-not-allowed');
+ });
+ document.querySelectorAll('.role-canEditPolicy-input, .role-canEditRoles-input').forEach(el => {
+ el.disabled = false;
+ el.classList.remove('cursor-not-allowed');
+ });
+ document.querySelectorAll('.delete-rbac-role-btn').forEach(el => el.classList.remove('hidden'));
+ document.getElementById('add-role-btn')?.classList.remove('hidden');
+
+ // Enable defaults inputs
+ const langInput = document.getElementById('defaults-language-input');
+ if (langInput) {
+ langInput.disabled = false;
+ langInput.classList.remove('bg-gray-200', 'cursor-not-allowed');
+ }
+ document.getElementById('add-language-btn')?.classList.remove('hidden');
+ const defaultLang = document.getElementById('defaults-default-language');
+ if (defaultLang) {
+ defaultLang.disabled = false;
+ defaultLang.classList.remove('bg-gray-200', 'cursor-not-allowed');
+ }
+ const severity = document.getElementById('defaults-severity');
+ if (severity) {
+ severity.disabled = false;
+ severity.classList.remove('bg-gray-200', 'cursor-not-allowed');
+ }
+ // Show remove buttons on language tags
+ document.querySelectorAll('.remove-language-btn').forEach(el => el.classList.remove('hidden'));
+
+ // Show rule add/edit/delete buttons
+ document.getElementById('add-rule-btn')?.classList.remove('hidden');
+ document.getElementById('add-rule-btn-bottom')?.classList.remove('hidden');
+ document.querySelectorAll('.delete-rule-btn').forEach(el => el.classList.remove('hidden'));
+
+ // Enable rule inputs
+ document.querySelectorAll('.say-input, .category-select, .language-select, .example-input').forEach(el => {
+ el.disabled = false;
+ el.classList.remove('bg-gray-200', 'cursor-not-allowed');
+ });
+ } else {
// Hide save buttons
document.getElementById('save-btn')?.classList.add('hidden');
document.getElementById('floating-save-btn')?.classList.add('hidden');
@@ -1316,13 +1319,22 @@ function applyPermissions() {
document.getElementById('add-role-btn')?.classList.add('hidden');
// Disable defaults inputs
- document.getElementById('defaults-language-input').disabled = true;
- document.getElementById('defaults-language-input').classList.add('bg-gray-200', 'cursor-not-allowed');
+ const langInput = document.getElementById('defaults-language-input');
+ if (langInput) {
+ langInput.disabled = true;
+ langInput.classList.add('bg-gray-200', 'cursor-not-allowed');
+ }
document.getElementById('add-language-btn')?.classList.add('hidden');
- document.getElementById('defaults-default-language').disabled = true;
- document.getElementById('defaults-default-language').classList.add('bg-gray-200', 'cursor-not-allowed');
- document.getElementById('defaults-severity').disabled = true;
- document.getElementById('defaults-severity').classList.add('bg-gray-200', 'cursor-not-allowed');
+ const defaultLang = document.getElementById('defaults-default-language');
+ if (defaultLang) {
+ defaultLang.disabled = true;
+ defaultLang.classList.add('bg-gray-200', 'cursor-not-allowed');
+ }
+ const severity = document.getElementById('defaults-severity');
+ if (severity) {
+ severity.disabled = true;
+ severity.classList.add('bg-gray-200', 'cursor-not-allowed');
+ }
// Hide remove buttons on language tags
document.querySelectorAll('.remove-language-btn').forEach(el => el.classList.add('hidden'));
@@ -1338,61 +1350,29 @@ function applyPermissions() {
});
}
- // When canEditRoles = false: Read-only user management
- if (!canEditRoles) {
- // Hide "Add User" button
- document.getElementById('add-user-btn')?.classList.add('hidden');
-
- // Hide user delete buttons
- document.querySelectorAll('.delete-user-btn').forEach(el => el.classList.add('hidden'));
-
- // Disable role dropdown
- document.querySelectorAll('.user-role-select').forEach(el => {
- el.disabled = true;
- el.classList.add('bg-gray-200', 'cursor-not-allowed');
- });
- }
-
- // Add read-only badge if any permission is missing
- if (!canEditPolicy || !canEditRoles) {
- const roleBadgeContainer = document.getElementById('user-role-badge').parentElement;
- if (!document.getElementById('readonly-badge')) {
- const readonlyBadge = document.createElement('span');
- readonlyBadge.id = 'readonly-badge';
- readonlyBadge.className = 'badge text-xs px-2 py-1 rounded-full bg-gray-500 text-white flex items-center gap-1';
- readonlyBadge.innerHTML = `
-

- 읽기 전용
- `;
- readonlyBadge.title = !canEditPolicy && !canEditRoles ? '정책과 역할 모두 읽기 전용입니다' :
- !canEditPolicy ? '정책이 읽기 전용입니다' :
- '역할이 읽기 전용입니다';
- roleBadgeContainer.appendChild(readonlyBadge);
- }
- }
+ // Clean up deprecated UI elements
+ const existingReadonlyBadge = document.getElementById('readonly-badge');
+ if (existingReadonlyBadge) existingReadonlyBadge.remove();
}
// ==================== Initialize ====================
async function init() {
try {
- // Load current user
+ // Load current user and role
appState.currentUser = await API.getMe();
- document.getElementById('user-name').textContent = appState.currentUser.username;
- document.getElementById('user-avatar').textContent = appState.currentUser.username.charAt(0).toUpperCase();
-
- const roleBadge = document.getElementById('user-role-badge');
- roleBadge.textContent = appState.currentUser.role;
- roleBadge.className = `badge text-xs px-2 py-1 rounded-full ${
- appState.currentUser.role === 'admin' ? 'bg-yellow-400 text-slate-900' :
- appState.currentUser.role === 'developer' ? 'bg-blue-500 text-white' :
- 'bg-gray-400 text-white'
- }`;
-
- // Load repo info
- const repoInfo = await API.getRepoInfo();
- document.getElementById('repo-info').textContent = `📁 ${repoInfo.owner}/${repoInfo.repo}`;
-
- // Load policy and users
+
+ // Display current role in header
+ updateUserRoleBadge(appState.currentUser.role || '역할 미선택');
+ updatePermissionBadges(appState.currentUser.permissions);
+
+ // Load project info
+ const projectInfo = await API.getProjectInfo();
+ document.getElementById('repo-info').textContent = `📁 ${projectInfo.project}`;
+
+ // Load available roles for selection
+ appState.availableRoles = await API.getAvailableRoles();
+
+ // Load policy
await loadPolicy();
await loadSettings();
await loadTemplates();
@@ -1428,10 +1408,6 @@ document.addEventListener('DOMContentLoaded', () => {
showModal('template-modal');
});
- // User management
- document.getElementById('add-user-btn').addEventListener('click', handleAddUser);
- document.getElementById('user-search').addEventListener('input', () => renderUsers());
-
// Rules management
document.getElementById('add-rule-btn').addEventListener('click', handleAddRule);
document.getElementById('add-rule-btn-bottom').addEventListener('click', handleAddRule);
diff --git a/internal/server/static/styles/output.css b/internal/server/static/styles/output.css
index 843cdd6..168431f 100644
--- a/internal/server/static/styles/output.css
+++ b/internal/server/static/styles/output.css
@@ -1 +1 @@
-*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.18 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.fixed{position:fixed}.inset-0{inset:0}.bottom-8{bottom:2rem}.right-8{right:2rem}.z-50{z-index:50}.mx-2{margin-left:.5rem;margin-right:.5rem}.mx-auto{margin-left:auto;margin-right:auto}.-ml-2{margin-left:-.5rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.mr-3{margin-right:.75rem}.mr-4{margin-right:1rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-16{height:4rem}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-9{height:2.25rem}.max-h-96{max-height:24rem}.min-h-\[32px\]{min-height:32px}.w-10{width:2.5rem}.w-16{width:4rem}.w-3{width:.75rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-9{width:2.25rem}.w-full{width:100%}.min-w-0{min-width:0}.max-w-2xl{max-width:42rem}.max-w-6xl{max-width:72rem}.flex-1{flex:1 1 0%}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-l-4{border-left-width:4px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-blue-200{--tw-border-opacity:1;border-color:rgb(191 219 254/var(--tw-border-opacity,1))}.border-blue-500{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity,1))}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity,1))}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity,1))}.border-gray-400{--tw-border-opacity:1;border-color:rgb(156 163 175/var(--tw-border-opacity,1))}.bg-amber-50{--tw-bg-opacity:1;background-color:rgb(255 251 235/var(--tw-bg-opacity,1))}.bg-blue-100{--tw-bg-opacity:1;background-color:rgb(219 234 254/var(--tw-bg-opacity,1))}.bg-blue-50{--tw-bg-opacity:1;background-color:rgb(239 246 255/var(--tw-bg-opacity,1))}.bg-blue-500{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.bg-blue-600{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity,1))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity,1))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity,1))}.bg-gray-400{--tw-bg-opacity:1;background-color:rgb(156 163 175/var(--tw-bg-opacity,1))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.bg-gray-500{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity,1))}.bg-gray-600{--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity,1))}.bg-green-500{--tw-bg-opacity:1;background-color:rgb(34 197 94/var(--tw-bg-opacity,1))}.bg-green-600{--tw-bg-opacity:1;background-color:rgb(22 163 74/var(--tw-bg-opacity,1))}.bg-purple-600{--tw-bg-opacity:1;background-color:rgb(147 51 234/var(--tw-bg-opacity,1))}.bg-red-500{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity,1))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-yellow-400{--tw-bg-opacity:1;background-color:rgb(250 204 21/var(--tw-bg-opacity,1))}.bg-yellow-500{--tw-bg-opacity:1;background-color:rgb(234 179 8/var(--tw-bg-opacity,1))}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.from-blue-400{--tw-gradient-from:#60a5fa var(--tw-gradient-from-position);--tw-gradient-to:rgba(96,165,250,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-blue-500{--tw-gradient-from:#3b82f6 var(--tw-gradient-from-position);--tw-gradient-to:rgba(59,130,246,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-purple-500{--tw-gradient-to:#a855f7 var(--tw-gradient-to-position)}.to-purple-600{--tw-gradient-to:#9333ea var(--tw-gradient-to-position)}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-2{padding-bottom:.5rem}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.italic{font-style:italic}.leading-none{line-height:1}.text-amber-600{--tw-text-opacity:1;color:rgb(217 119 6/var(--tw-text-opacity,1))}.text-amber-800{--tw-text-opacity:1;color:rgb(146 64 14/var(--tw-text-opacity,1))}.text-blue-600{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity,1))}.text-blue-800{--tw-text-opacity:1;color:rgb(30 64 175/var(--tw-text-opacity,1))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity,1))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity,1))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.text-purple-600{--tw-text-opacity:1;color:rgb(147 51 234/var(--tw-text-opacity,1))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity,1))}.text-slate-400{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.text-slate-500{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity,1))}.text-slate-600{--tw-text-opacity:1;color:rgb(71 85 105/var(--tw-text-opacity,1))}.text-slate-700{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity,1))}.text-slate-800{--tw-text-opacity:1;color:rgb(30 41 59/var(--tw-text-opacity,1))}.text-slate-900{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.shadow-2xl{--tw-shadow:0 25px 50px -12px rgba(0,0,0,.25);--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color)}.shadow-2xl,.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.shadow-md,.shadow-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-2,.ring-4{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-4{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-yellow-400{--tw-ring-opacity:1;--tw-ring-color:rgb(250 204 21/var(--tw-ring-opacity,1))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}@font-face{font-family:Pretendard;font-weight:400;font-style:normal;src:url(/fonts/Pretendard-Regular.woff2) format("woff2");font-display:swap}@font-face{font-family:Pretendard;font-weight:500;font-style:normal;src:url(/fonts/Pretendard-Medium.woff2) format("woff2");font-display:swap}@font-face{font-family:Pretendard;font-weight:700;font-style:normal;src:url(/fonts/Pretendard-Bold.woff2) format("woff2");font-display:swap}body,html{font-family:Pretendard,-apple-system,BlinkMacSystemFont,system-ui,Roboto,Helvetica Neue,Segoe UI,Apple SD Gothic Neo,Noto Sans KR,Malgun Gothic,sans-serif}::-webkit-scrollbar{width:8px}::-webkit-scrollbar-track{background:#e2e8f0}::-webkit-scrollbar-thumb{background:#94a3b8;border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#64748b}summary::marker{color:#64748b}details[open]>summary{border-bottom-color:#cbd5e1}.toast{position:fixed;top:20px;right:20px;z-index:1000;transition:all .3s}.spinner{border:3px solid #f3f3f3;border-top-color:#3498db;border-radius:50%;width:20px;height:20px;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.modal-overlay{background:rgba(0,0,0,.5);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}@keyframes fadeIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@keyframes checkmark{0%{transform:scale(0) rotate(0deg)}50%{transform:scale(1.2) rotate(180deg)}to{transform:scale(1) rotate(1turn)}}.fade-in{animation:fadeIn .6s ease-out}.checkmark{animation:checkmark .8s ease-out}.hover\:scale-110:hover{--tw-scale-x:1.1;--tw-scale-y:1.1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:border-blue-500:hover{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity,1))}.hover\:bg-blue-50:hover{--tw-bg-opacity:1;background-color:rgb(239 246 255/var(--tw-bg-opacity,1))}.hover\:bg-blue-500:hover{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity,1))}.hover\:bg-gray-200:hover{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity,1))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.hover\:bg-gray-500:hover{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity,1))}.hover\:bg-green-500:hover{--tw-bg-opacity:1;background-color:rgb(34 197 94/var(--tw-bg-opacity,1))}.hover\:bg-purple-500:hover{--tw-bg-opacity:1;background-color:rgb(168 85 247/var(--tw-bg-opacity,1))}.hover\:bg-red-50:hover{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.hover\:text-blue-600:hover{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity,1))}.hover\:text-red-500:hover{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity,1))}.hover\:text-slate-800:hover{--tw-text-opacity:1;color:rgb(30 41 59/var(--tw-text-opacity,1))}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.focus\:border-blue-500:focus{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity,1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-1:focus,.focus\:ring-2:focus{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-blue-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(59 130 246/var(--tw-ring-opacity,1))}@media (min-width:640px){.sm\:h-10{height:2.5rem}.sm\:w-10{width:2.5rem}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:p-6{padding:1.5rem}.sm\:text-4xl{font-size:2.25rem;line-height:2.5rem}}@media (min-width:768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width:1024px){.lg\:p-8{padding:2rem}}
\ No newline at end of file
+*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.18 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.\!container{width:100%!important}.container{width:100%}@media (min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media (min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media (min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media (min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media (min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.fixed{position:fixed}.inset-0{inset:0}.bottom-8{bottom:2rem}.right-8{right:2rem}.z-50{z-index:50}.mx-2{margin-left:.5rem;margin-right:.5rem}.mx-auto{margin-left:auto;margin-right:auto}.-ml-2{margin-left:-.5rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.mr-3{margin-right:.75rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.block{display:block}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-16{height:4rem}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-9{height:2.25rem}.max-h-96{max-height:24rem}.min-h-\[32px\]{min-height:32px}.w-16{width:4rem}.w-3{width:.75rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-9{width:2.25rem}.w-full{width:100%}.min-w-0{min-width:0}.max-w-2xl{max-width:42rem}.max-w-6xl{max-width:72rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-l-4{border-left-width:4px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-blue-200{--tw-border-opacity:1;border-color:rgb(191 219 254/var(--tw-border-opacity,1))}.border-blue-300{--tw-border-opacity:1;border-color:rgb(147 197 253/var(--tw-border-opacity,1))}.border-blue-500{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity,1))}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity,1))}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity,1))}.border-gray-400{--tw-border-opacity:1;border-color:rgb(156 163 175/var(--tw-border-opacity,1))}.border-green-300{--tw-border-opacity:1;border-color:rgb(134 239 172/var(--tw-border-opacity,1))}.border-indigo-300{--tw-border-opacity:1;border-color:rgb(165 180 252/var(--tw-border-opacity,1))}.border-orange-300{--tw-border-opacity:1;border-color:rgb(253 186 116/var(--tw-border-opacity,1))}.border-pink-300{--tw-border-opacity:1;border-color:rgb(249 168 212/var(--tw-border-opacity,1))}.border-purple-300{--tw-border-opacity:1;border-color:rgb(216 180 254/var(--tw-border-opacity,1))}.border-red-300{--tw-border-opacity:1;border-color:rgb(252 165 165/var(--tw-border-opacity,1))}.border-teal-300{--tw-border-opacity:1;border-color:rgb(94 234 212/var(--tw-border-opacity,1))}.border-yellow-300{--tw-border-opacity:1;border-color:rgb(253 224 71/var(--tw-border-opacity,1))}.bg-amber-50{--tw-bg-opacity:1;background-color:rgb(255 251 235/var(--tw-bg-opacity,1))}.bg-blue-100{--tw-bg-opacity:1;background-color:rgb(219 234 254/var(--tw-bg-opacity,1))}.bg-blue-50{--tw-bg-opacity:1;background-color:rgb(239 246 255/var(--tw-bg-opacity,1))}.bg-blue-500{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.bg-blue-600{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity,1))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity,1))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity,1))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.bg-gray-500{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity,1))}.bg-gray-600{--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity,1))}.bg-green-100{--tw-bg-opacity:1;background-color:rgb(220 252 231/var(--tw-bg-opacity,1))}.bg-green-500{--tw-bg-opacity:1;background-color:rgb(34 197 94/var(--tw-bg-opacity,1))}.bg-green-600{--tw-bg-opacity:1;background-color:rgb(22 163 74/var(--tw-bg-opacity,1))}.bg-indigo-100{--tw-bg-opacity:1;background-color:rgb(224 231 255/var(--tw-bg-opacity,1))}.bg-orange-100{--tw-bg-opacity:1;background-color:rgb(255 237 213/var(--tw-bg-opacity,1))}.bg-pink-100{--tw-bg-opacity:1;background-color:rgb(252 231 243/var(--tw-bg-opacity,1))}.bg-purple-100{--tw-bg-opacity:1;background-color:rgb(243 232 255/var(--tw-bg-opacity,1))}.bg-purple-600{--tw-bg-opacity:1;background-color:rgb(147 51 234/var(--tw-bg-opacity,1))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity,1))}.bg-red-500{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity,1))}.bg-slate-700{--tw-bg-opacity:1;background-color:rgb(51 65 85/var(--tw-bg-opacity,1))}.bg-teal-100{--tw-bg-opacity:1;background-color:rgb(204 251 241/var(--tw-bg-opacity,1))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-yellow-100{--tw-bg-opacity:1;background-color:rgb(254 249 195/var(--tw-bg-opacity,1))}.bg-yellow-500{--tw-bg-opacity:1;background-color:rgb(234 179 8/var(--tw-bg-opacity,1))}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-2{padding-bottom:.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.leading-none{line-height:1}.text-amber-600{--tw-text-opacity:1;color:rgb(217 119 6/var(--tw-text-opacity,1))}.text-amber-800{--tw-text-opacity:1;color:rgb(146 64 14/var(--tw-text-opacity,1))}.text-blue-600{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity,1))}.text-blue-700{--tw-text-opacity:1;color:rgb(29 78 216/var(--tw-text-opacity,1))}.text-blue-800{--tw-text-opacity:1;color:rgb(30 64 175/var(--tw-text-opacity,1))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity,1))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity,1))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.text-green-700{--tw-text-opacity:1;color:rgb(21 128 61/var(--tw-text-opacity,1))}.text-indigo-700{--tw-text-opacity:1;color:rgb(67 56 202/var(--tw-text-opacity,1))}.text-orange-700{--tw-text-opacity:1;color:rgb(194 65 12/var(--tw-text-opacity,1))}.text-pink-700{--tw-text-opacity:1;color:rgb(190 24 93/var(--tw-text-opacity,1))}.text-purple-600{--tw-text-opacity:1;color:rgb(147 51 234/var(--tw-text-opacity,1))}.text-purple-700{--tw-text-opacity:1;color:rgb(126 34 206/var(--tw-text-opacity,1))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity,1))}.text-slate-400{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity,1))}.text-slate-500{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity,1))}.text-slate-600{--tw-text-opacity:1;color:rgb(71 85 105/var(--tw-text-opacity,1))}.text-slate-700{--tw-text-opacity:1;color:rgb(51 65 85/var(--tw-text-opacity,1))}.text-slate-800{--tw-text-opacity:1;color:rgb(30 41 59/var(--tw-text-opacity,1))}.text-slate-900{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity,1))}.text-teal-700{--tw-text-opacity:1;color:rgb(15 118 110/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-yellow-700{--tw-text-opacity:1;color:rgb(161 98 7/var(--tw-text-opacity,1))}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-70{opacity:.7}.shadow-2xl{--tw-shadow:0 25px 50px -12px rgba(0,0,0,.25);--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color)}.shadow-2xl,.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.shadow-md,.shadow-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-2,.ring-4{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-4{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-yellow-400{--tw-ring-opacity:1;--tw-ring-color:rgb(250 204 21/var(--tw-ring-opacity,1))}.ring-opacity-50{--tw-ring-opacity:0.5}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}@font-face{font-family:Pretendard;font-weight:400;font-style:normal;src:url(/fonts/Pretendard-Regular.woff2) format("woff2");font-display:swap}@font-face{font-family:Pretendard;font-weight:500;font-style:normal;src:url(/fonts/Pretendard-Medium.woff2) format("woff2");font-display:swap}@font-face{font-family:Pretendard;font-weight:700;font-style:normal;src:url(/fonts/Pretendard-Bold.woff2) format("woff2");font-display:swap}body,html{font-family:Pretendard,-apple-system,BlinkMacSystemFont,system-ui,Roboto,Helvetica Neue,Segoe UI,Apple SD Gothic Neo,Noto Sans KR,Malgun Gothic,sans-serif}::-webkit-scrollbar{width:8px}::-webkit-scrollbar-track{background:#e2e8f0}::-webkit-scrollbar-thumb{background:#94a3b8;border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#64748b}summary::marker{color:#64748b}details[open]>summary{border-bottom-color:#cbd5e1}.toast{position:fixed;top:20px;right:20px;z-index:1000;transition:all .3s}.spinner{border:3px solid #f3f3f3;border-top-color:#3498db;border-radius:50%;width:20px;height:20px;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.modal-overlay{background:rgba(0,0,0,.5);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}@keyframes fadeIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@keyframes checkmark{0%{transform:scale(0) rotate(0deg)}50%{transform:scale(1.2) rotate(180deg)}to{transform:scale(1) rotate(1turn)}}.fade-in{animation:fadeIn .6s ease-out}.checkmark{animation:checkmark .8s ease-out}.hover\:scale-110:hover{--tw-scale-x:1.1;--tw-scale-y:1.1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:border-blue-500:hover{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity,1))}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity,1))}.hover\:bg-blue-50:hover{--tw-bg-opacity:1;background-color:rgb(239 246 255/var(--tw-bg-opacity,1))}.hover\:bg-blue-500:hover{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.hover\:bg-gray-200:hover{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity,1))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.hover\:bg-gray-500:hover{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity,1))}.hover\:bg-green-500:hover{--tw-bg-opacity:1;background-color:rgb(34 197 94/var(--tw-bg-opacity,1))}.hover\:bg-purple-500:hover{--tw-bg-opacity:1;background-color:rgb(168 85 247/var(--tw-bg-opacity,1))}.hover\:text-blue-600:hover{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity,1))}.hover\:text-red-500:hover{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity,1))}.hover\:text-slate-800:hover{--tw-text-opacity:1;color:rgb(30 41 59/var(--tw-text-opacity,1))}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.focus\:border-blue-500:focus{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity,1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-1:focus,.focus\:ring-2:focus{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-blue-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(59 130 246/var(--tw-ring-opacity,1))}@media (min-width:640px){.sm\:h-10{height:2.5rem}.sm\:w-10{width:2.5rem}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:p-6{padding:1.5rem}.sm\:text-4xl{font-size:2.25rem;line-height:2.5rem}}@media (min-width:768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width:1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:p-8{padding:2rem}}
\ No newline at end of file
diff --git a/internal/validator/validator.go b/internal/validator/validator.go
index 0327e58..b3294a8 100644
--- a/internal/validator/validator.go
+++ b/internal/validator/validator.go
@@ -12,7 +12,6 @@ import (
"github.com/DevSymphony/sym-cli/internal/adapter"
adapterRegistry "github.com/DevSymphony/sym-cli/internal/adapter/registry"
- "github.com/DevSymphony/sym-cli/internal/git"
"github.com/DevSymphony/sym-cli/internal/llm"
"github.com/DevSymphony/sym-cli/internal/roles"
"github.com/DevSymphony/sym-cli/pkg/schema"
@@ -360,10 +359,10 @@ func (v *Validator) ValidateChanges(ctx context.Context, changes []GitChange) (*
// Check RBAC permissions first
if v.policy.Enforce.RBACConfig != nil && v.policy.Enforce.RBACConfig.Enabled {
- username, err := git.GetCurrentUser()
- if err == nil {
+ currentRole, err := roles.GetCurrentRole()
+ if err == nil && currentRole != "" {
if v.verbose {
- fmt.Printf("🔐 Checking RBAC permissions for user: %s\n", username)
+ fmt.Printf("🔐 Checking RBAC permissions for role: %s\n", currentRole)
}
changedFiles := make([]string, 0, len(changes))
@@ -374,13 +373,13 @@ func (v *Validator) ValidateChanges(ctx context.Context, changes []GitChange) (*
}
if len(changedFiles) > 0 {
- rbacResult, err := roles.ValidateFilePermissions(username, changedFiles)
+ rbacResult, err := roles.ValidateFilePermissionsForRole(currentRole, changedFiles)
if err == nil && !rbacResult.Allowed {
for _, deniedFile := range rbacResult.DeniedFiles {
result.Violations = append(result.Violations, Violation{
RuleID: "rbac-permission-denied",
Severity: "error",
- Message: fmt.Sprintf("User '%s' does not have permission to modify this file", username),
+ Message: fmt.Sprintf("Role '%s' does not have permission to modify this file", currentRole),
File: deniedFile,
Line: 0,
Column: 0,