diff --git a/src/systems/dashboard/dashboardIntegration.js b/src/systems/dashboard/dashboardIntegration.js
index 3d8f299..79ef448 100644
--- a/src/systems/dashboard/dashboardIntegration.js
+++ b/src/systems/dashboard/dashboardIntegration.js
@@ -27,6 +27,7 @@ import { registerSceneInfoWidget } from './widgets/sceneInfoWidget.js';
import { registerPresentCharactersWidget } from './widgets/presentCharactersWidget.js';
import { registerInventoryWidget } from './widgets/inventoryWidget.js';
import { registerQuestsWidget } from './widgets/questsWidget.js';
+import { registerUserSkillsWidget } from './widgets/userSkillsWidget.js';
// Global dashboard manager instance
let dashboardManager = null;
@@ -254,6 +255,9 @@ function registerAllWidgets(registry, dependencies) {
// Quest widget
registerQuestsWidget(registry, dependencies);
+ // Skills widget
+ registerUserSkillsWidget(registry, dependencies);
+
console.log(`[RPG Companion] Registered ${registry.getAll().length} widgets`);
}
diff --git a/src/systems/dashboard/dashboardManager.js b/src/systems/dashboard/dashboardManager.js
index d6e5f32..289b63e 100644
--- a/src/systems/dashboard/dashboardManager.js
+++ b/src/systems/dashboard/dashboardManager.js
@@ -949,7 +949,8 @@ export class DashboardManager {
scene: [],
social: [],
inventory: [],
- quests: []
+ quests: [],
+ skills: []
};
widgets.forEach(widget => {
@@ -1031,6 +1032,19 @@ export class DashboardManager {
this.gridEngine.autoLayout(groups.quests, { preserveOrder: true });
}
+ // Create Skills tab if there are skills widgets
+ if (groups.skills.length > 0) {
+ this.dashboard.tabs.push({
+ id: 'tab-skills',
+ name: 'Skills',
+ icon: 'fa-solid fa-book',
+ order: 5,
+ widgets: groups.skills
+ });
+
+ this.gridEngine.autoLayout(groups.skills, { preserveOrder: true });
+ }
+
console.log('[DashboardManager] Created', this.dashboard.tabs.length, 'tabs');
// Re-render tabs and switch to first tab
@@ -1070,7 +1084,8 @@ export class DashboardManager {
'social': 3,
'inventory': 4,
'quests': 5,
- 'other': 6
+ 'skills': 6,
+ 'other': 7
};
// Specific widget type ordering within user category
diff --git a/src/systems/dashboard/defaultLayout.js b/src/systems/dashboard/defaultLayout.js
index aac58d5..31232b9 100644
--- a/src/systems/dashboard/defaultLayout.js
+++ b/src/systems/dashboard/defaultLayout.js
@@ -167,6 +167,29 @@ export function generateDefaultDashboard() {
}
}
]
+ },
+ // Tab 5: Skills (Full tab for skills system)
+ {
+ id: 'tab-skills',
+ name: 'Skills',
+ icon: 'fa-solid fa-book',
+ order: 4,
+ widgets: [
+ {
+ id: 'widget-userskills',
+ type: 'userSkills',
+ x: 0,
+ y: 0,
+ w: 3,
+ h: 7,
+ config: {
+ defaultSubTab: 'all',
+ showXP: true,
+ showCategories: true,
+ maxLevel: 10
+ }
+ }
+ ]
}
],
diff --git a/src/systems/dashboard/widgets/userSkillsWidget.js b/src/systems/dashboard/widgets/userSkillsWidget.js
new file mode 100644
index 0000000..ea6608a
--- /dev/null
+++ b/src/systems/dashboard/widgets/userSkillsWidget.js
@@ -0,0 +1,1079 @@
+/**
+ * User Skills Widget
+ *
+ * Comprehensive skills tracking system with categories, levels, and XP progress.
+ * Features three sub-tabs, multiple view modes, and full CRUD operations.
+ *
+ * Data Model:
+ * skills: {
+ * version: 1,
+ * categories: {
+ * 'Combat': [{ name: 'Swordsmanship', level: 5, xp: 75, maxXP: 100 }, ...],
+ * 'Magic': [...]
+ * },
+ * uncategorized: [...]
+ * }
+ */
+
+import { parseItems, serializeItems } from '../../../utils/itemParser.js';
+
+// Per-widget state storage (Map: widgetId => state)
+const widgetStates = new Map();
+
+/**
+ * Get or initialize widget state
+ */
+function getWidgetState(widgetId) {
+ if (!widgetStates.has(widgetId)) {
+ widgetStates.set(widgetId, {
+ activeSubTab: 'all',
+ viewModes: {
+ all: 'list',
+ categories: 'list',
+ quick: 'grid'
+ },
+ collapsedCategories: [],
+ sortBy: 'level', // 'level', 'name', 'xp'
+ filterText: ''
+ });
+ }
+ return widgetStates.get(widgetId);
+}
+
+/**
+ * Migrate old string format to structured format
+ */
+function migrateSkillsData(oldSkills) {
+ // Already in new format
+ if (oldSkills && typeof oldSkills === 'object' && oldSkills.version) {
+ return oldSkills;
+ }
+
+ // Old string format: "Swordsmanship, Lockpicking, Alchemy"
+ if (typeof oldSkills === 'string' && oldSkills.trim()) {
+ const skillNames = parseItems(oldSkills);
+ return {
+ version: 1,
+ categories: {},
+ uncategorized: skillNames.map(name => ({
+ name,
+ level: 1,
+ xp: 0,
+ maxXP: 100
+ }))
+ };
+ }
+
+ // Empty or null
+ return {
+ version: 1,
+ categories: {},
+ uncategorized: []
+ };
+}
+
+/**
+ * Get all skills as flat array
+ */
+function getAllSkills(skillsData) {
+ const skills = [];
+
+ // Add skills from categories
+ for (const [category, categorySkills] of Object.entries(skillsData.categories || {})) {
+ categorySkills.forEach(skill => {
+ skills.push({ ...skill, category });
+ });
+ }
+
+ // Add uncategorized skills
+ (skillsData.uncategorized || []).forEach(skill => {
+ skills.push({ ...skill, category: null });
+ });
+
+ return skills;
+}
+
+/**
+ * Sort skills
+ */
+function sortSkills(skills, sortBy) {
+ const sorted = [...skills];
+
+ switch (sortBy) {
+ case 'level':
+ sorted.sort((a, b) => b.level - a.level || a.name.localeCompare(b.name));
+ break;
+ case 'name':
+ sorted.sort((a, b) => a.name.localeCompare(b.name));
+ break;
+ case 'xp':
+ sorted.sort((a, b) => {
+ const progressA = a.xp / a.maxXP;
+ const progressB = b.xp / b.maxXP;
+ return progressB - progressA || b.level - a.level;
+ });
+ break;
+ }
+
+ return sorted;
+}
+
+/**
+ * Filter skills by search text
+ */
+function filterSkills(skills, filterText) {
+ if (!filterText.trim()) return skills;
+
+ const search = filterText.toLowerCase();
+ return skills.filter(skill =>
+ skill.name.toLowerCase().includes(search) ||
+ (skill.category && skill.category.toLowerCase().includes(search))
+ );
+}
+
+/**
+ * Sanitize skill name
+ */
+function sanitizeSkillName(name) {
+ return name.trim().replace(/[<>]/g, '').slice(0, 100);
+}
+
+/**
+ * Sanitize category name
+ */
+function sanitizeCategoryName(name) {
+ return name.trim().replace(/[<>]/g, '').slice(0, 50);
+}
+
+/**
+ * Register User Skills Widget
+ */
+export function registerUserSkillsWidget(registry, dependencies) {
+ const { getExtensionSettings, onDataChange } = dependencies;
+
+ registry.register('userSkills', {
+ name: 'User Skills',
+ icon: '⚔️',
+ description: 'Character skills with categories, levels, and XP tracking',
+ category: 'skills',
+ minSize: { w: 2, h: 4 },
+ // Large widget like Inventory/Quests
+ defaultSize: (columns) => {
+ if (columns <= 2) {
+ return { w: 2, h: 6 }; // Mobile: 2 cols (full), 6 rows
+ }
+ return { w: 3, h: 7 }; // Desktop: 3 cols (full), 7 rows
+ },
+ maxAutoSize: (columns) => {
+ if (columns <= 2) {
+ return { w: 2, h: 8 };
+ }
+ return { w: 3, h: 10 };
+ },
+ requiresSchema: false,
+
+ /**
+ * Render widget content
+ */
+ render(container, config = {}) {
+ const settings = getExtensionSettings();
+ const skillsConfig = settings.trackerConfig?.userStats?.skillsSection;
+
+ // Check if skills tracking is enabled
+ if (!skillsConfig?.enabled) {
+ container.innerHTML = `
+
+ `;
+ return;
+ }
+
+ // Migrate and get skills data
+ let skillsData = settings.userStats?.skills;
+ skillsData = migrateSkillsData(skillsData);
+
+ // Save migrated data
+ if (!settings.userStats) settings.userStats = {};
+ settings.userStats.skills = skillsData;
+
+ // Get widget ID from container
+ const widgetId = container.closest('[data-widget-id]')?.dataset.widgetId || 'default';
+ const state = getWidgetState(widgetId);
+
+ // Build UI based on active sub-tab
+ const html = renderSkillsUI(skillsData, state, config, widgetId);
+ container.innerHTML = html;
+
+ // Attach event handlers
+ attachSkillsHandlers(container, widgetId, dependencies, config);
+ },
+
+ /**
+ * Get widget configuration schema
+ */
+ getConfig() {
+ return {
+ showXP: {
+ type: 'boolean',
+ label: 'Show XP Progress Bars',
+ default: true,
+ description: 'Display XP progress bars for each skill'
+ },
+ showCategories: {
+ type: 'boolean',
+ label: 'Show Category Tags',
+ default: true,
+ description: 'Show category labels on skill cards'
+ },
+ defaultSort: {
+ type: 'select',
+ label: 'Default Sort Order',
+ options: [
+ { value: 'level', label: 'By Level (High to Low)' },
+ { value: 'name', label: 'By Name (A-Z)' },
+ { value: 'xp', label: 'By XP Progress' }
+ ],
+ default: 'level',
+ description: 'How to sort skills in All Skills view'
+ },
+ maxLevel: {
+ type: 'number',
+ label: 'Maximum Skill Level',
+ default: 10,
+ min: 1,
+ max: 100,
+ description: 'Highest level a skill can reach'
+ }
+ };
+ },
+
+ /**
+ * Handle widget resize
+ */
+ onResize(container, newW, newH) {
+ // Add compact class for narrow widths
+ if (newW <= 2) {
+ container.classList.add('rpg-skills-compact');
+ container.classList.remove('rpg-skills-wide');
+ } else {
+ container.classList.add('rpg-skills-wide');
+ container.classList.remove('rpg-skills-compact');
+ }
+ }
+ });
+}
+
+/**
+ * Render skills UI
+ */
+function renderSkillsUI(skillsData, state, config, widgetId) {
+ const allSkills = getAllSkills(skillsData);
+ const hasSkills = allSkills.length > 0;
+
+ let html = ''; // Close rpg-skills-widget
+ return html;
+}
+
+/**
+ * Render sub-tab navigation
+ */
+function renderSubTabs(activeTab) {
+ const tabs = [
+ { id: 'all', label: 'All', icon: 'fa-list' },
+ { id: 'categories', label: 'By Category', icon: 'fa-folder-tree' },
+ { id: 'quick', label: 'Quick', icon: 'fa-bolt' }
+ ];
+
+ let html = '';
+ tabs.forEach(tab => {
+ const active = tab.id === activeTab ? 'active' : '';
+ html += `
+
+ `;
+ });
+ html += '
';
+
+ return html;
+}
+
+/**
+ * Render All Skills tab
+ */
+function renderAllSkillsTab(skillsData, state, config) {
+ const allSkills = getAllSkills(skillsData);
+
+ let html = '';
+
+ // Header with controls
+ html += `
+
+ `;
+
+ // Search/filter
+ html += `
+
+
+
+
+ `;
+
+ // Add skill form (hidden by default)
+ html += renderAddSkillForm(skillsData);
+
+ // Skills list/grid
+ if (allSkills.length === 0) {
+ html += `
+
+
+
No skills yet
+
Click the + button to add your first skill
+
+ `;
+ } else {
+ let filtered = filterSkills(allSkills, state.filterText);
+ let sorted = sortSkills(filtered, state.sortBy);
+
+ const viewMode = state.viewModes.all;
+ html += `
`;
+ sorted.forEach(skill => {
+ html += renderSkillCard(skill, config, viewMode);
+ });
+ html += '
';
+
+ if (filtered.length === 0 && allSkills.length > 0) {
+ html += `
+
+
+
No skills match your search
+
+ `;
+ }
+ }
+
+ html += '
';
+ return html;
+}
+
+/**
+ * Render By Category tab
+ */
+function renderCategoriesTab(skillsData, state, config) {
+ let html = '';
+
+ // Header
+ html += `
+
+ `;
+
+ // Add category form (hidden)
+ html += renderAddCategoryForm();
+
+ const viewMode = state.viewModes.categories;
+ const categories = Object.keys(skillsData.categories || {}).sort();
+ const uncategorized = skillsData.uncategorized || [];
+
+ if (categories.length === 0 && uncategorized.length === 0) {
+ html += `
+
+
+
No categories yet
+
Click the folder+ button to create a category
+
+ `;
+ } else {
+ // Render categories
+ categories.forEach(category => {
+ html += renderCategory(category, skillsData.categories[category], state, config, viewMode);
+ });
+
+ // Render uncategorized
+ if (uncategorized.length > 0) {
+ html += renderCategory('Uncategorized', uncategorized, state, config, viewMode, true);
+ }
+ }
+
+ html += '
';
+ return html;
+}
+
+/**
+ * Render Quick View tab
+ */
+function renderQuickViewTab(skillsData, state, config) {
+ const allSkills = getAllSkills(skillsData);
+ const topSkills = allSkills.sort((a, b) => b.level - a.level).slice(0, 12);
+
+ let html = '';
+
+ html += `
+
+ `;
+
+ html += `
+
+ Showing your top skills for quick reference
+
`;
+
+ if (topSkills.length === 0) {
+ html += `
+
+
+
No skills to display
+
Add skills in the "All Skills" tab
+
+ `;
+ } else {
+ html += '
';
+ topSkills.forEach(skill => {
+ html += renderSkillCard(skill, config, 'quick');
+ });
+ html += '
';
+ }
+
+ html += '
';
+ return html;
+}
+
+/**
+ * Render category section
+ */
+function renderCategory(categoryName, skills, state, config, viewMode, isUncategorized = false) {
+ const isCollapsed = state.collapsedCategories.includes(categoryName);
+
+ let html = '';
+
+ // Category header
+ html += `
+
+ `;
+
+ // Category content
+ if (!isCollapsed) {
+ html += renderAddSkillForm(null, categoryName, true);
+ html += `
`;
+ skills.forEach(skill => {
+ html += renderSkillCard({ ...skill, category: isUncategorized ? null : categoryName }, config, viewMode);
+ });
+ html += '
';
+ }
+
+ html += '
';
+ return html;
+}
+
+/**
+ * Render skill card
+ */
+function renderSkillCard(skill, config, viewMode) {
+ const xpPercent = (skill.xp / skill.maxXP) * 100;
+ const showXP = config.showXP !== false;
+ const showCategory = config.showCategories !== false && skill.category;
+ const isQuickView = viewMode === 'quick';
+
+ let html = ``;
+
+ // Skill info wrapper (name, level, XP bar)
+ html += '
';
+
+ // Header row with name and level
+ html += '';
+
+ // XP bar (if not quick view)
+ if (showXP && !isQuickView) {
+ html += `
+
+
+
${skill.xp}/${skill.maxXP} XP
+
+ `;
+ }
+
+ html += '
'; // Close rpg-skill-info
+
+ // Actions
+ html += '
';
+ if (!isQuickView) {
+ html += `
+
+
+
+ `;
+ } else {
+ html += `
+
+ `;
+ }
+ html += '
';
+
+ html += '
';
+ return html;
+}
+
+/**
+ * Render add skill form
+ */
+function renderAddSkillForm(skillsData, targetCategory = null, isInCategory = false) {
+ const categories = skillsData ? Object.keys(skillsData.categories || {}).sort() : [];
+
+ let html = `';
+ return html;
+}
+
+/**
+ * Render add category form
+ */
+function renderAddCategoryForm() {
+ let html = '';
+ return html;
+}
+
+/**
+ * Attach event handlers
+ */
+function attachSkillsHandlers(container, widgetId, dependencies, config) {
+ const { getExtensionSettings, onDataChange } = dependencies;
+
+ // Check if handlers are already attached to prevent duplicate listeners
+ if (container.dataset.handlersAttached === 'true') {
+ return;
+ }
+ container.dataset.handlersAttached = 'true';
+
+ // Event delegation
+ container.addEventListener('click', (e) => {
+ const target = e.target.closest('[data-action]');
+ if (!target) return;
+
+ const action = target.dataset.action;
+ handleAction(action, target, container, widgetId, dependencies, config);
+ });
+
+ // Filter input
+ const filterInput = container.querySelector('.rpg-filter-input');
+ if (filterInput) {
+ filterInput.addEventListener('input', (e) => {
+ const state = getWidgetState(widgetId);
+ state.filterText = e.target.value;
+ rerender(container, widgetId, dependencies, config);
+ });
+ }
+
+ // Sort select
+ const sortSelect = container.querySelector('.rpg-sort-select');
+ if (sortSelect) {
+ sortSelect.addEventListener('change', (e) => {
+ const state = getWidgetState(widgetId);
+ state.sortBy = e.target.value;
+ rerender(container, widgetId, dependencies, config);
+ });
+ }
+
+ // Skill name editing
+ container.addEventListener('blur', (e) => {
+ if (e.target.hasAttribute('contenteditable') && e.target.dataset.action === 'edit-skill-name') {
+ const skillName = e.target.dataset.original;
+ const newName = sanitizeSkillName(e.target.textContent);
+
+ if (newName && newName !== skillName) {
+ updateSkillName(skillName, e.target.closest('.rpg-skill-card').dataset.category, newName, dependencies);
+ rerender(container, widgetId, dependencies, config);
+ } else {
+ e.target.textContent = skillName;
+ }
+ }
+ }, true);
+
+ // Keyboard shortcuts
+ container.addEventListener('keydown', (e) => {
+ if (e.key === 'Enter') {
+ const target = e.target;
+ if (target.classList.contains('rpg-inline-input')) {
+ e.preventDefault();
+ const saveBtn = target.closest('.rpg-inline-form').querySelector('.rpg-inline-save');
+ if (saveBtn) saveBtn.click();
+ } else if (target.hasAttribute('contenteditable')) {
+ e.preventDefault();
+ target.blur();
+ }
+ } else if (e.key === 'Escape') {
+ const target = e.target;
+ if (target.classList.contains('rpg-inline-input')) {
+ const cancelBtn = target.closest('.rpg-inline-form').querySelector('.rpg-inline-cancel');
+ if (cancelBtn) cancelBtn.click();
+ } else if (target.hasAttribute('contenteditable')) {
+ const original = target.dataset.original;
+ target.textContent = original;
+ target.blur();
+ }
+ }
+ });
+}
+
+/**
+ * Handle actions
+ */
+function handleAction(action, target, container, widgetId, dependencies, config) {
+ const settings = dependencies.getExtensionSettings();
+ const state = getWidgetState(widgetId);
+
+ switch (action) {
+ case 'switch-tab':
+ state.activeSubTab = target.dataset.tab;
+ rerender(container, widgetId, dependencies, config);
+ break;
+
+ case 'change-view':
+ state.viewModes[target.dataset.tab] = target.dataset.view;
+ rerender(container, widgetId, dependencies, config);
+ break;
+
+ case 'show-add-skill':
+ showAddSkillForm(container);
+ break;
+
+ case 'show-add-skill-to-category':
+ showAddSkillForm(container, target.dataset.category);
+ break;
+
+ case 'cancel-add-skill':
+ hideAddSkillForm(container, target);
+ break;
+
+ case 'save-add-skill':
+ saveNewSkill(container, target, dependencies);
+ rerender(container, widgetId, dependencies, config);
+ break;
+
+ case 'show-add-category':
+ showAddCategoryForm(container);
+ break;
+
+ case 'cancel-add-category':
+ hideAddCategoryForm(container);
+ break;
+
+ case 'save-add-category':
+ saveNewCategory(container, dependencies);
+ rerender(container, widgetId, dependencies, config);
+ break;
+
+ case 'toggle-category':
+ toggleCategory(target.dataset.category, state);
+ rerender(container, widgetId, dependencies, config);
+ break;
+
+ case 'level-up':
+ levelUpSkill(target, dependencies);
+ rerender(container, widgetId, dependencies, config);
+ break;
+
+ case 'level-down':
+ levelDownSkill(target, dependencies);
+ rerender(container, widgetId, dependencies, config);
+ break;
+
+ case 'delete-skill':
+ deleteSkill(target, dependencies);
+ rerender(container, widgetId, dependencies, config);
+ break;
+
+ case 'delete-category':
+ deleteCategory(target.dataset.category, dependencies);
+ rerender(container, widgetId, dependencies, config);
+ break;
+ }
+}
+
+/**
+ * Show add skill form
+ */
+function showAddSkillForm(container, targetCategory = null) {
+ const form = targetCategory
+ ? container.querySelector(`.rpg-add-skill-form[data-target-category="${targetCategory}"]`)
+ : container.querySelector('.rpg-add-skill-form:not([data-target-category])');
+
+ if (form) {
+ form.style.display = 'block';
+ const input = form.querySelector('input[data-field="name"]');
+ if (input) input.focus();
+ }
+}
+
+/**
+ * Hide add skill form
+ */
+function hideAddSkillForm(container, cancelBtn) {
+ const form = cancelBtn.closest('.rpg-add-skill-form');
+ if (form) {
+ form.style.display = 'none';
+ form.querySelectorAll('input').forEach(input => input.value = '');
+ }
+}
+
+/**
+ * Save new skill
+ */
+function saveNewSkill(container, saveBtn, dependencies) {
+ const form = saveBtn.closest('.rpg-add-skill-form');
+ const nameInput = form.querySelector('[data-field="name"]');
+ const levelInput = form.querySelector('[data-field="level"]');
+ const categorySelect = form.querySelector('[data-field="category"]');
+
+ const name = sanitizeSkillName(nameInput.value);
+ const level = parseInt(levelInput.value) || 1;
+ const category = form.dataset.targetCategory || (categorySelect ? categorySelect.value : null);
+
+ if (!name) return;
+
+ const settings = dependencies.getExtensionSettings();
+ const skillsData = settings.userStats.skills;
+
+ const newSkill = {
+ name,
+ level: Math.max(1, Math.min(100, level)),
+ xp: 0,
+ maxXP: 100
+ };
+
+ if (category && category !== '' && category !== 'Uncategorized') {
+ if (!skillsData.categories[category]) {
+ skillsData.categories[category] = [];
+ }
+ skillsData.categories[category].push(newSkill);
+ } else {
+ skillsData.uncategorized.push(newSkill);
+ }
+
+ saveSkillsData(settings, skillsData, dependencies);
+
+ form.style.display = 'none';
+ form.querySelectorAll('input').forEach(input => input.value = '');
+}
+
+/**
+ * Show add category form
+ */
+function showAddCategoryForm(container) {
+ const form = container.querySelector('.rpg-add-category-form');
+ if (form) {
+ form.style.display = 'block';
+ const input = form.querySelector('input');
+ if (input) input.focus();
+ }
+}
+
+/**
+ * Hide add category form
+ */
+function hideAddCategoryForm(container) {
+ const form = container.querySelector('.rpg-add-category-form');
+ if (form) {
+ form.style.display = 'none';
+ form.querySelector('input').value = '';
+ }
+}
+
+/**
+ * Save new category
+ */
+function saveNewCategory(container, dependencies) {
+ const form = container.querySelector('.rpg-add-category-form');
+ const input = form.querySelector('input');
+ const name = sanitizeCategoryName(input.value);
+
+ if (!name) return;
+
+ const settings = dependencies.getExtensionSettings();
+ const skillsData = settings.userStats.skills;
+
+ if (!skillsData.categories[name]) {
+ skillsData.categories[name] = [];
+ saveSkillsData(settings, skillsData, dependencies);
+ }
+
+ form.style.display = 'none';
+ input.value = '';
+}
+
+/**
+ * Toggle category collapsed state
+ */
+function toggleCategory(categoryName, state) {
+ const index = state.collapsedCategories.indexOf(categoryName);
+ if (index >= 0) {
+ state.collapsedCategories.splice(index, 1);
+ } else {
+ state.collapsedCategories.push(categoryName);
+ }
+}
+
+/**
+ * Level up skill
+ */
+function levelUpSkill(target, dependencies) {
+ const card = target.closest('.rpg-skill-card');
+ const skillName = card.dataset.skill;
+ const category = card.dataset.category === 'Uncategorized' ? null : card.dataset.category;
+
+ const settings = dependencies.getExtensionSettings();
+ const skillsData = settings.userStats.skills;
+
+ const skill = findSkill(skillsData, skillName, category);
+ if (skill) {
+ skill.level++;
+ skill.xp = 0; // Reset XP on level up
+ saveSkillsData(settings, skillsData, dependencies);
+ }
+}
+
+/**
+ * Level down skill
+ */
+function levelDownSkill(target, dependencies) {
+ const card = target.closest('.rpg-skill-card');
+ const skillName = card.dataset.skill;
+ const category = card.dataset.category === 'Uncategorized' ? null : card.dataset.category;
+
+ const settings = dependencies.getExtensionSettings();
+ const skillsData = settings.userStats.skills;
+
+ const skill = findSkill(skillsData, skillName, category);
+ if (skill && skill.level > 1) {
+ skill.level--;
+ skill.xp = 0; // Reset XP on level change
+ saveSkillsData(settings, skillsData, dependencies);
+ }
+}
+
+/**
+ * Delete skill
+ */
+function deleteSkill(target, dependencies) {
+ const card = target.closest('.rpg-skill-card');
+ const skillName = card.dataset.skill;
+ const category = card.dataset.category === 'Uncategorized' ? null : card.dataset.category;
+
+ const settings = dependencies.getExtensionSettings();
+ const skillsData = settings.userStats.skills;
+
+ removeSkill(skillsData, skillName, category);
+ saveSkillsData(settings, skillsData, dependencies);
+}
+
+/**
+ * Delete category
+ */
+function deleteCategory(categoryName, dependencies) {
+ const settings = dependencies.getExtensionSettings();
+ const skillsData = settings.userStats.skills;
+
+ if (skillsData.categories[categoryName]) {
+ // Move skills to uncategorized
+ const skills = skillsData.categories[categoryName];
+ skillsData.uncategorized.push(...skills);
+ delete skillsData.categories[categoryName];
+ saveSkillsData(settings, skillsData, dependencies);
+ }
+}
+
+/**
+ * Update skill name
+ */
+function updateSkillName(oldName, category, newName, dependencies) {
+ const settings = dependencies.getExtensionSettings();
+ const skillsData = settings.userStats.skills;
+
+ const skill = findSkill(skillsData, oldName, category === 'Uncategorized' ? null : category);
+ if (skill) {
+ skill.name = newName;
+ saveSkillsData(settings, skillsData, dependencies);
+ }
+}
+
+/**
+ * Find skill in data
+ */
+function findSkill(skillsData, name, category) {
+ if (category) {
+ const categorySkills = skillsData.categories[category];
+ return categorySkills ? categorySkills.find(s => s.name === name) : null;
+ } else {
+ return skillsData.uncategorized.find(s => s.name === name);
+ }
+}
+
+/**
+ * Remove skill from data
+ */
+function removeSkill(skillsData, name, category) {
+ if (category) {
+ const categorySkills = skillsData.categories[category];
+ if (categorySkills) {
+ const index = categorySkills.findIndex(s => s.name === name);
+ if (index >= 0) categorySkills.splice(index, 1);
+ }
+ } else {
+ const index = skillsData.uncategorized.findIndex(s => s.name === name);
+ if (index >= 0) skillsData.uncategorized.splice(index, 1);
+ }
+}
+
+/**
+ * Save skills data
+ */
+function saveSkillsData(settings, skillsData, dependencies) {
+ settings.userStats.skills = skillsData;
+
+ if (dependencies.onDataChange) {
+ dependencies.onDataChange('userStats', 'skills', skillsData);
+ }
+}
+
+/**
+ * Re-render widget
+ */
+function rerender(container, widgetId, dependencies, config) {
+ const settings = dependencies.getExtensionSettings();
+ const skillsData = settings.userStats.skills;
+ const state = getWidgetState(widgetId);
+
+ const html = renderSkillsUI(skillsData, state, config, widgetId);
+ container.innerHTML = html;
+
+ attachSkillsHandlers(container, widgetId, dependencies, config);
+}
diff --git a/style.css b/style.css
index 38eb097..5cafea4 100644
--- a/style.css
+++ b/style.css
@@ -4370,6 +4370,82 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
color: var(--rpg-highlight);
}
+/* Apply theme colors to skills subtabs */
+.rpg-panel[data-theme="sci-fi"] .rpg-skills-subtabs,
+.rpg-panel[data-theme="fantasy"] .rpg-skills-subtabs,
+.rpg-panel[data-theme="cyberpunk"] .rpg-skills-subtabs {
+ border-bottom-color: var(--rpg-border);
+}
+
+.rpg-panel[data-theme="sci-fi"] .rpg-skills-subtab,
+.rpg-panel[data-theme="fantasy"] .rpg-skills-subtab,
+.rpg-panel[data-theme="cyberpunk"] .rpg-skills-subtab {
+ border-color: var(--rpg-border);
+ color: var(--rpg-text);
+}
+
+.rpg-panel[data-theme="sci-fi"] .rpg-skills-subtab:hover,
+.rpg-panel[data-theme="fantasy"] .rpg-skills-subtab:hover,
+.rpg-panel[data-theme="cyberpunk"] .rpg-skills-subtab:hover {
+ border-color: var(--rpg-highlight);
+ color: var(--rpg-highlight);
+}
+
+.rpg-panel[data-theme="sci-fi"] .rpg-skills-subtab.active,
+.rpg-panel[data-theme="fantasy"] .rpg-skills-subtab.active,
+.rpg-panel[data-theme="cyberpunk"] .rpg-skills-subtab.active {
+ border-color: var(--rpg-highlight);
+ color: var(--rpg-highlight);
+}
+
+/* Apply theme colors to skill cards */
+.rpg-panel[data-theme="sci-fi"] .rpg-skill-card,
+.rpg-panel[data-theme="fantasy"] .rpg-skill-card,
+.rpg-panel[data-theme="cyberpunk"] .rpg-skill-card {
+ border-color: var(--rpg-border);
+}
+
+.rpg-panel[data-theme="sci-fi"] .rpg-skill-card:hover,
+.rpg-panel[data-theme="fantasy"] .rpg-skill-card:hover,
+.rpg-panel[data-theme="cyberpunk"] .rpg-skill-card:hover {
+ border-color: var(--rpg-highlight);
+}
+
+/* Apply theme colors to category headers */
+.rpg-panel[data-theme="sci-fi"] .rpg-category-header,
+.rpg-panel[data-theme="fantasy"] .rpg-category-header,
+.rpg-panel[data-theme="cyberpunk"] .rpg-category-header {
+ background: var(--rpg-highlight);
+ border-color: var(--rpg-border);
+}
+
+.rpg-panel[data-theme="sci-fi"] .rpg-category-name,
+.rpg-panel[data-theme="fantasy"] .rpg-category-name,
+.rpg-panel[data-theme="cyberpunk"] .rpg-category-name {
+ color: var(--rpg-text);
+}
+
+/* Apply theme colors to XP bars */
+.rpg-panel[data-theme="sci-fi"] .rpg-xp-bar,
+.rpg-panel[data-theme="fantasy"] .rpg-xp-bar,
+.rpg-panel[data-theme="cyberpunk"] .rpg-xp-bar {
+ border-color: var(--rpg-border);
+}
+
+.rpg-panel[data-theme="sci-fi"] .rpg-xp-fill,
+.rpg-panel[data-theme="fantasy"] .rpg-xp-fill,
+.rpg-panel[data-theme="cyberpunk"] .rpg-xp-fill {
+ background: linear-gradient(90deg, var(--rpg-highlight), var(--rpg-accent));
+}
+
+/* Apply theme colors to skills add button */
+.rpg-panel[data-theme="sci-fi"] .rpg-skills-add-btn,
+.rpg-panel[data-theme="fantasy"] .rpg-skills-add-btn,
+.rpg-panel[data-theme="cyberpunk"] .rpg-skills-add-btn {
+ border-color: var(--rpg-highlight);
+ color: var(--rpg-highlight);
+}
+
/* Apply theme colors to storage locations */
.rpg-panel[data-theme="sci-fi"] .rpg-storage-location,
.rpg-panel[data-theme="fantasy"] .rpg-storage-location,
@@ -7723,6 +7799,628 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
color: var(--rpg-highlight);
}
+/* ============================================
+ SKILLS WIDGET STYLES
+ ============================================ */
+
+/* Skills Widget - Flex container for proper scrolling */
+.rpg-skills-widget {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ min-height: 0;
+ overflow: hidden;
+}
+
+/* Skills Views - Scrollable content area */
+.rpg-skills-views {
+ flex: 1;
+ min-height: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+/* Skills Sub-tabs Navigation */
+.rpg-skills-subtabs {
+ display: flex;
+ gap: 0.5rem;
+ border-bottom: 2px solid var(--SmartThemeBorderColor);
+ padding-bottom: 0.5rem;
+ overflow-x: auto;
+ overflow-y: hidden;
+ flex-wrap: nowrap;
+ scrollbar-width: thin;
+ scrollbar-color: var(--SmartThemeBorderColor) transparent;
+}
+
+.rpg-skills-subtabs::-webkit-scrollbar {
+ height: 6px;
+}
+
+.rpg-skills-subtabs::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+.rpg-skills-subtabs::-webkit-scrollbar-thumb {
+ background: var(--SmartThemeBorderColor);
+ border-radius: 3px;
+}
+
+.rpg-skills-subtabs::-webkit-scrollbar-thumb:hover {
+ background: var(--rpg-accent);
+}
+
+.rpg-skills-subtab {
+ flex: 1;
+ min-width: fit-content;
+ white-space: nowrap;
+ padding: 0.5rem 1rem;
+ background: transparent;
+ border: 2px solid var(--SmartThemeBorderColor);
+ border-radius: 0.25rem;
+ color: var(--SmartThemeBodyColor);
+ cursor: pointer;
+ transition: all 0.2s ease;
+ font-weight: 500;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+}
+
+.rpg-skills-subtab i {
+ font-size: 1rem;
+}
+
+.rpg-skills-subtab:hover {
+ background: rgba(var(--rpg-highlight-rgb, 233, 69, 96), 0.1);
+ border-color: var(--rpg-highlight);
+ color: var(--rpg-highlight);
+}
+
+.rpg-skills-subtab.active {
+ background: transparent;
+ border-color: var(--rpg-highlight);
+ color: var(--rpg-highlight);
+ font-weight: 600;
+}
+
+/* Skills Sections */
+.rpg-skills-section {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+}
+
+.rpg-skills-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding-bottom: 0.5rem;
+ border-bottom: 1px solid var(--SmartThemeBorderColor);
+ gap: 0.5rem;
+ overflow-x: auto;
+ overflow-y: hidden;
+ flex-wrap: nowrap;
+ scrollbar-width: thin;
+ scrollbar-color: var(--SmartThemeBorderColor) transparent;
+}
+
+.rpg-skills-header::-webkit-scrollbar {
+ height: 6px;
+}
+
+.rpg-skills-header::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+.rpg-skills-header::-webkit-scrollbar-thumb {
+ background: var(--SmartThemeBorderColor);
+ border-radius: 3px;
+}
+
+.rpg-skills-header::-webkit-scrollbar-thumb:hover {
+ background: var(--rpg-accent);
+}
+
+.rpg-skills-header h4 {
+ margin: 0;
+ font-size: 1.1rem;
+ color: var(--SmartThemeBodyColor);
+ white-space: nowrap;
+ min-width: fit-content;
+}
+
+.rpg-skills-content {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+}
+
+/* Skills Add Button */
+.rpg-skills-add-btn {
+ padding: 0.4rem 0.75rem;
+ border: 1px solid var(--SmartThemeBorderColor);
+ border-radius: 0.25rem;
+ background: transparent;
+ border-color: var(--rpg-highlight);
+ color: var(--rpg-highlight);
+ white-space: nowrap;
+ min-width: fit-content;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ font-size: 0.85rem;
+ display: flex;
+ align-items: center;
+ gap: 0.35rem;
+}
+
+.rpg-skills-add-btn:hover {
+ background: rgba(var(--rpg-highlight-rgb, 233, 69, 96), 0.1);
+ border-color: var(--rpg-highlight);
+}
+
+/* Skills Empty State */
+.rpg-skills-empty {
+ padding: 2rem;
+ text-align: center;
+ color: var(--SmartThemeFastUISliderColColor);
+ font-style: italic;
+ font-size: 0.9rem;
+}
+
+/* Skills Filter */
+.rpg-skills-filter {
+ margin-bottom: 0.75rem;
+}
+
+.rpg-filter-input {
+ width: 100%;
+ padding: 0.5rem 0.75rem;
+ background: var(--SmartThemeBlurTintColor);
+ border: 1px solid var(--SmartThemeBorderColor);
+ border-radius: 0.25rem;
+ color: var(--SmartThemeBodyColor);
+ font-size: 0.9rem;
+ font-family: inherit;
+}
+
+.rpg-filter-input:focus {
+ outline: none;
+ border-color: var(--ac-style-color-matchedText);
+ box-shadow: 0 0 0 2px rgba(var(--ac-style-color-matchedText-rgb, 66, 135, 245), 0.2);
+}
+
+.rpg-filter-input::placeholder {
+ color: var(--SmartThemeFastUISliderColColor);
+ font-style: italic;
+}
+
+/* Category Headers (Collapsible) */
+.rpg-category-header {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.5rem;
+ background: var(--SmartThemeQuoteColor);
+ border: 1px solid var(--SmartThemeBorderColor);
+ border-radius: 0.25rem;
+ cursor: pointer;
+ margin-top: 0.5rem;
+}
+
+.rpg-category-toggle {
+ background: none;
+ border: none;
+ color: var(--SmartThemeBodyColor);
+ cursor: pointer;
+ padding: 0.25rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: transform 0.2s ease;
+}
+
+.rpg-category-toggle:hover {
+ color: var(--ac-style-color-matchedText);
+}
+
+.rpg-category-toggle i {
+ transition: transform 0.2s ease;
+}
+
+.rpg-category-header.collapsed .rpg-category-toggle i {
+ transform: rotate(-90deg);
+}
+
+.rpg-category-name {
+ flex: 1;
+ margin: 0;
+ font-size: 1rem;
+ font-weight: 600;
+ color: #000000;
+}
+
+.rpg-category-actions {
+ display: flex;
+ gap: 0.5rem;
+}
+
+.rpg-category-content {
+ margin-top: 0.75rem;
+}
+
+.rpg-category-header.collapsed + .rpg-category-content {
+ display: none;
+}
+
+/* Skill Cards - List and Grid Views */
+.rpg-skills-list {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ min-height: 2rem;
+ padding: 0.5rem 0;
+}
+
+.rpg-skill-card {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ padding: 0.75rem 1rem;
+ background: transparent;
+ border: 2px solid var(--rpg-highlight);
+ border-radius: 0.25rem;
+ color: var(--SmartThemeBodyColor);
+ font-size: 0.95rem;
+ transition: all 0.2s ease;
+}
+
+.rpg-skill-card:hover {
+ border-color: var(--rpg-highlight);
+ background: rgba(var(--rpg-highlight-rgb, 233, 69, 96), 0.1);
+}
+
+.rpg-skill-info {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 0.35rem;
+ min-width: 0;
+}
+
+.rpg-skill-header-row {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+}
+
+.rpg-skill-name {
+ font-weight: 500;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ cursor: text;
+}
+
+.rpg-skill-name.rpg-editable {
+ border-bottom: 1px dashed var(--SmartThemeBorderColor);
+ transition: all 0.2s ease;
+}
+
+.rpg-skill-name.rpg-editable:hover {
+ border-bottom-color: var(--ac-style-color-matchedText);
+}
+
+.rpg-skill-name.rpg-editable:focus {
+ outline: none;
+ border-bottom-color: var(--ac-style-color-matchedText);
+ background: var(--SmartThemeQuoteColor);
+}
+
+.rpg-skill-level {
+ font-size: 0.85rem;
+ color: var(--rpg-highlight);
+ font-weight: 600;
+ white-space: nowrap;
+}
+
+.rpg-skill-actions {
+ display: flex;
+ gap: 0.5rem;
+ align-items: center;
+ flex-shrink: 0;
+}
+
+.rpg-skill-action {
+ padding: 0.3rem 0.6rem;
+ background: transparent;
+ border: 1px solid var(--SmartThemeBorderColor);
+ border-radius: 0.25rem;
+ color: var(--SmartThemeBodyColor);
+ cursor: pointer;
+ transition: all 0.2s ease;
+ font-size: 0.85rem;
+ white-space: nowrap;
+}
+
+.rpg-skill-action:hover {
+ background: var(--ac-style-color-matchedText);
+ border-color: var(--ac-style-color-matchedText);
+ color: white;
+}
+
+.rpg-skill-action.rpg-level-up-btn {
+ border-color: var(--rpg-highlight);
+ color: var(--rpg-highlight);
+}
+
+.rpg-skill-action.rpg-level-up-btn:hover {
+ background: var(--rpg-highlight);
+ color: white;
+}
+
+.rpg-skill-action.rpg-level-down-btn {
+ border-color: var(--rpg-accent);
+ color: var(--rpg-accent);
+}
+
+.rpg-skill-action.rpg-level-down-btn:hover {
+ background: var(--rpg-accent);
+ color: white;
+}
+
+.rpg-skill-action.rpg-delete-btn {
+ color: var(--SmartThemeFastUISliderColColor);
+}
+
+.rpg-skill-action.rpg-delete-btn:hover {
+ background: #dc3545;
+ border-color: #dc3545;
+ color: white;
+}
+
+/* XP Progress Bar */
+.rpg-xp-bar {
+ position: relative;
+ width: 100%;
+ height: 1.25rem;
+ background: var(--SmartThemeBlurTintColor);
+ border: 1px solid var(--SmartThemeBorderColor);
+ border-radius: 0.25rem;
+ overflow: hidden;
+}
+
+.rpg-xp-fill {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100%;
+ background: linear-gradient(90deg,
+ var(--rpg-highlight),
+ rgba(var(--rpg-highlight-rgb, 233, 69, 96), 0.7));
+ transition: width 0.3s ease;
+ border-radius: 0.25rem 0 0 0.25rem;
+}
+
+.rpg-xp-text {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.75rem;
+ font-weight: 600;
+ color: var(--SmartThemeBodyColor);
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
+}
+
+/* Grid View for Skills */
+.rpg-skills-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+ gap: 0.75rem;
+ padding: 0.5rem 0;
+}
+
+.rpg-skills-grid .rpg-skill-card {
+ flex-direction: column;
+ align-items: stretch;
+ padding: 1rem 0.75rem;
+ min-height: 100px;
+}
+
+.rpg-skills-grid .rpg-skill-info {
+ align-items: center;
+ text-align: center;
+}
+
+.rpg-skills-grid .rpg-skill-header-row {
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.rpg-skills-grid .rpg-skill-name {
+ text-align: center;
+ word-wrap: break-word;
+ overflow-wrap: break-word;
+ white-space: normal;
+ max-width: 100%;
+}
+
+.rpg-skills-grid .rpg-skill-actions {
+ flex-direction: column;
+ width: 100%;
+}
+
+.rpg-skills-grid .rpg-skill-action {
+ width: 100%;
+}
+
+/* Quick View - Compact List */
+.rpg-skills-quick-list {
+ display: flex;
+ flex-direction: column;
+ gap: 0.35rem;
+ padding: 0.5rem 0;
+}
+
+.rpg-skills-quick-list .rpg-skill-card {
+ padding: 0.5rem 0.75rem;
+ gap: 0.5rem;
+}
+
+.rpg-skills-quick-list .rpg-skill-info {
+ gap: 0;
+}
+
+.rpg-skills-quick-list .rpg-xp-bar {
+ display: none;
+}
+
+.rpg-skills-quick-list .rpg-skill-action {
+ padding: 0.25rem 0.5rem;
+ font-size: 0.8rem;
+}
+
+/* Inline Forms for Skills and Categories */
+.rpg-add-skill-form,
+.rpg-add-category-form {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ padding: 0.75rem;
+ background: var(--SmartThemeQuoteColor);
+ border: 1px solid var(--ac-style-color-matchedText);
+ border-radius: 0.25rem;
+ margin-bottom: 0.75rem;
+}
+
+/* Header Actions (View Toggle + Add Button) */
+.rpg-skills-header-actions {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ flex-wrap: nowrap;
+ min-width: fit-content;
+}
+
+/* Sort and Filter Controls */
+.rpg-skills-controls {
+ display: flex;
+ gap: 0.75rem;
+ align-items: center;
+ flex-wrap: wrap;
+ margin-bottom: 0.75rem;
+}
+
+.rpg-sort-dropdown {
+ padding: 0.4rem 0.75rem;
+ background: var(--SmartThemeBlurTintColor);
+ border: 1px solid var(--SmartThemeBorderColor);
+ border-radius: 0.25rem;
+ color: var(--SmartThemeBodyColor);
+ font-size: 0.85rem;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.rpg-sort-dropdown:hover {
+ border-color: var(--ac-style-color-matchedText);
+}
+
+.rpg-sort-dropdown:focus {
+ outline: none;
+ border-color: var(--ac-style-color-matchedText);
+ box-shadow: 0 0 0 2px rgba(var(--ac-style-color-matchedText-rgb, 66, 135, 245), 0.2);
+}
+
+/* Responsive Classes - Wide Layout */
+.rpg-skills-wide .rpg-skills-header h4 {
+ font-size: 1.2rem;
+}
+
+.rpg-skills-wide .rpg-skill-card {
+ padding: 1rem 1.25rem;
+}
+
+/* Responsive Classes - Compact Layout */
+.rpg-skills-compact .rpg-skills-header h4 {
+ font-size: 1rem;
+}
+
+.rpg-skills-compact .rpg-skill-card {
+ padding: 0.5rem 0.75rem;
+ gap: 0.75rem;
+}
+
+.rpg-skills-compact .rpg-skills-add-btn {
+ font-size: 0.8rem;
+ padding: 0.35rem 0.6rem;
+}
+
+.rpg-skills-compact .rpg-skill-action {
+ padding: 0.25rem 0.5rem;
+ font-size: 0.8rem;
+}
+
+.rpg-skills-compact .rpg-xp-bar {
+ height: 1rem;
+}
+
+.rpg-skills-compact .rpg-xp-text {
+ font-size: 0.7rem;
+}
+
+/* Mobile Responsiveness for Skills */
+@media (max-width: 768px) {
+ .rpg-skills-subtabs {
+ gap: 0.35rem;
+ }
+
+ .rpg-skills-subtab {
+ padding: 0.4rem 0.75rem;
+ font-size: 0.85rem;
+ }
+
+ .rpg-skills-subtab .rpg-subtab-label {
+ display: none;
+ }
+
+ .rpg-skills-grid {
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+ gap: 0.5rem;
+ }
+
+ .rpg-skills-header {
+ flex-wrap: wrap;
+ }
+
+ .rpg-skills-header h4 {
+ font-size: 1rem;
+ }
+
+ .rpg-skill-card {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 0.75rem;
+ }
+
+ .rpg-skill-actions {
+ width: 100%;
+ flex-wrap: wrap;
+ }
+
+ .rpg-skill-action {
+ flex: 1;
+ min-width: fit-content;
+ }
+}
+
/* ============================================
DESKTOP TABS SYSTEM
============================================ */