@glossarist/concept-browser 0.4.5 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glossarist/concept-browser",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "Vue SPA for browsing Glossarist terminology datasets with cross-reference resolution, graph visualization, and multi-language support",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { computed } from 'vue';
2
+ import { computed, watch } from 'vue';
3
3
  import { useVocabularyStore } from '../stores/vocabulary';
4
4
  import { useUiStore } from '../stores/ui';
5
5
  import { useRoute, useRouter } from 'vue-router';
@@ -25,6 +25,7 @@ const {
25
25
  taxonomyLabels,
26
26
  treeRoots,
27
27
  supportingClasses,
28
+ isOverview,
28
29
  toggleExpand,
29
30
  childClasses,
30
31
  hasChildren,
@@ -72,6 +73,22 @@ function isActive(page: { route: string; datasetScoped?: boolean }): boolean {
72
73
  }
73
74
  return route.name === page.route;
74
75
  }
76
+
77
+ function selectClass(id: string) {
78
+ activeClassId.value = id;
79
+ activeTaxonomy.value = null;
80
+ scrollMainToTop();
81
+ }
82
+
83
+ function selectTaxonomy(key: string) {
84
+ activeTaxonomy.value = key;
85
+ scrollMainToTop();
86
+ }
87
+
88
+ function scrollMainToTop() {
89
+ const main = document.querySelector('main');
90
+ if (main) main.scrollTo({ top: 0, behavior: 'smooth' });
91
+ }
75
92
  </script>
76
93
 
77
94
  <template>
@@ -88,85 +105,92 @@ function isActive(page: { route: string; datasetScoped?: boolean }): boolean {
88
105
  <!-- Navigation -->
89
106
  <div class="section-label">Navigation</div>
90
107
  <nav class="space-y-0.5 mb-6">
91
- <router-link
92
- v-for="page in globalPages"
93
- :key="page.route || 'home'"
94
- :to="pageRoute(page)"
95
- class="btn-ghost w-full text-left flex items-center gap-2"
96
- :class="isActive(page) ? 'active' : ''"
97
- @click="closeMobile"
98
- >
99
- <NavIcon :name="page.icon" />
100
- {{ page.title }}
101
- </router-link>
102
- </nav>
108
+ <template v-for="page in globalPages" :key="page.route || 'home'">
109
+ <router-link
110
+ :to="pageRoute(page)"
111
+ class="btn-ghost w-full text-left flex items-center gap-2"
112
+ :class="isActive(page) ? 'active' : ''"
113
+ @click="closeMobile"
114
+ >
115
+ <NavIcon :name="page.icon" />
116
+ {{ page.title }}
117
+ </router-link>
103
118
 
104
- <!-- Ontology class tree (shown when on /ontology route) -->
105
- <div v-if="isOntologyRoute" class="mb-6">
106
- <div class="section-label">Classes</div>
107
- <nav class="space-y-0.5">
108
- <template v-for="root in treeRoots" :key="root.compact">
109
- <button @click="activeClassId = root.compact; activeTaxonomy = null; toggleExpand(root)"
119
+ <!-- Ontology class tree nested under Ontology nav item -->
120
+ <div v-if="page.route === 'ontology' && isOntologyRoute" class="ml-4 mt-1 mb-2 space-y-0.5">
121
+ <!-- Overview -->
122
+ <button @click="activeClassId = null; activeTaxonomy = null; scrollMainToTop()"
110
123
  class="w-full flex items-center gap-1.5 px-2 py-1.5 rounded-lg text-xs transition-colors"
111
- :class="activeClassId === root.compact && !activeTaxonomy ? 'bg-ink-800/8 text-blue-700 font-medium' : 'text-ink-600 hover:bg-ink-50'"
124
+ :class="isOverview ? 'bg-ink-800/8 text-blue-700 font-medium' : 'text-ink-600 hover:bg-ink-50'"
112
125
  >
113
- <span v-if="hasChildren(root)" class="text-[10px] text-ink-300 w-3">{{ expandedClasses.has(root.compact) ? '▾' : '▸' }}</span>
114
- <span v-else class="w-3"></span>
115
- <span class="flex-1 text-left">{{ root.label }}</span>
126
+ <span class="w-3 text-ink-200">·</span>
127
+ <span class="flex-1 text-left">Overview</span>
116
128
  </button>
117
- <!-- Children -->
118
- <div v-if="expandedClasses.has(root.compact) && hasChildren(root)" class="ml-3">
119
- <template v-for="child in childClasses(root.compact)" :key="child.compact">
120
- <button @click="activeClassId = child.compact; activeTaxonomy = null; toggleExpand(child)"
121
- class="w-full flex items-center gap-1.5 px-2 py-1 rounded-lg text-[11px] transition-colors"
122
- :class="activeClassId === child.compact && !activeTaxonomy ? 'bg-ink-800/8 text-blue-700 font-medium' : 'text-ink-500 hover:bg-ink-50'"
123
- >
124
- <span v-if="hasChildren(child)" class="text-[10px] text-ink-300 w-3">{{ expandedClasses.has(child.compact) ? '▾' : '▸' }}</span>
125
- <span v-else class="w-3 text-ink-200">·</span>
126
- <span class="flex-1 text-left">{{ child.label }}</span>
127
- </button>
128
- <!-- Grandchildren -->
129
- <div v-if="expandedClasses.has(child.compact) && hasChildren(child)" class="ml-3">
130
- <button v-for="gc in childClasses(child.compact)" :key="gc.compact"
131
- @click="activeClassId = gc.compact; activeTaxonomy = null"
129
+
130
+ <div class="text-[10px] uppercase tracking-wide text-ink-300 mt-2 mb-1 px-2">Classes</div>
131
+ <template v-for="root in treeRoots" :key="root.compact">
132
+ <button @click="selectClass(root.compact); toggleExpand(root)"
133
+ class="w-full flex items-center gap-1.5 px-2 py-1.5 rounded-lg text-xs transition-colors"
134
+ :class="activeClassId === root.compact && !activeTaxonomy ? 'bg-ink-800/8 text-blue-700 font-medium' : 'text-ink-600 hover:bg-ink-50'"
135
+ >
136
+ <span v-if="hasChildren(root)" class="text-[10px] text-ink-300 w-3">{{ expandedClasses.has(root.compact) ? '▾' : '▸' }}</span>
137
+ <span v-else class="w-3 text-ink-200">·</span>
138
+ <span class="flex-1 text-left">{{ root.label }}</span>
139
+ </button>
140
+ <!-- Children -->
141
+ <div v-if="expandedClasses.has(root.compact) && hasChildren(root)" class="ml-3">
142
+ <template v-for="child in childClasses(root.compact)" :key="child.compact">
143
+ <button @click="selectClass(child.compact); toggleExpand(child)"
132
144
  class="w-full flex items-center gap-1.5 px-2 py-1 rounded-lg text-[11px] transition-colors"
133
- :class="activeClassId === gc.compact && !activeTaxonomy ? 'bg-ink-800/8 text-blue-700 font-medium' : 'text-ink-400 hover:bg-ink-50'"
145
+ :class="activeClassId === child.compact && !activeTaxonomy ? 'bg-ink-800/8 text-blue-700 font-medium' : 'text-ink-500 hover:bg-ink-50'"
134
146
  >
135
- <span class="w-3 text-ink-200">·</span>
136
- <span class="flex-1 text-left">{{ gc.label }}</span>
147
+ <span v-if="hasChildren(child)" class="text-[10px] text-ink-300 w-3">{{ expandedClasses.has(child.compact) ? '▾' : '▸' }}</span>
148
+ <span v-else class="w-3 text-ink-200">·</span>
149
+ <span class="flex-1 text-left">{{ child.label }}</span>
137
150
  </button>
138
- </div>
139
- </template>
151
+ <!-- Grandchildren -->
152
+ <div v-if="expandedClasses.has(child.compact) && hasChildren(child)" class="ml-3">
153
+ <button v-for="gc in childClasses(child.compact)" :key="gc.compact"
154
+ @click="selectClass(gc.compact)"
155
+ class="w-full flex items-center gap-1.5 px-2 py-1 rounded-lg text-[11px] transition-colors"
156
+ :class="activeClassId === gc.compact && !activeTaxonomy ? 'bg-ink-800/8 text-blue-700 font-medium' : 'text-ink-400 hover:bg-ink-50'"
157
+ >
158
+ <span class="w-3 text-ink-200">·</span>
159
+ <span class="flex-1 text-left">{{ gc.label }}</span>
160
+ </button>
161
+ </div>
162
+ </template>
163
+ </div>
164
+ </template>
165
+
166
+ <!-- Supporting classes -->
167
+ <div v-if="supportingClasses.length" class="mt-2 pt-2 border-t border-ink-100/40">
168
+ <div class="text-[10px] uppercase tracking-wide text-ink-300 mb-1 px-2">Supporting</div>
169
+ <button v-for="cls in supportingClasses" :key="cls.compact"
170
+ @click="selectClass(cls.compact)"
171
+ class="w-full flex items-center gap-1.5 px-2 py-1 rounded-lg text-[11px] transition-colors"
172
+ :class="activeClassId === cls.compact && !activeTaxonomy ? 'bg-ink-800/8 text-blue-700 font-medium' : 'text-ink-400 hover:bg-ink-50'"
173
+ >
174
+ <span class="w-3 text-ink-200">·</span>
175
+ <span class="flex-1 text-left">{{ cls.label }}</span>
176
+ </button>
140
177
  </div>
141
- </template>
142
-
143
- <!-- Supporting classes -->
144
- <div v-if="supportingClasses.length" class="mt-3 pt-2 border-t border-ink-100/40">
145
- <div class="text-[10px] uppercase tracking-wide text-ink-300 mb-1 px-2">Supporting</div>
146
- <button v-for="cls in supportingClasses" :key="cls.compact"
147
- @click="activeClassId = cls.compact; activeTaxonomy = null"
148
- class="w-full flex items-center gap-1.5 px-2 py-1 rounded-lg text-[11px] transition-colors"
149
- :class="activeClassId === cls.compact && !activeTaxonomy ? 'bg-ink-800/8 text-blue-700 font-medium' : 'text-ink-400 hover:bg-ink-50'"
150
- >
151
- <span class="w-3 text-ink-200">·</span>
152
- <span class="flex-1 text-left">{{ cls.label }}</span>
153
- </button>
154
- </div>
155
178
 
156
- <!-- SKOS Taxonomies -->
157
- <div class="mt-3 pt-2 border-t border-ink-100/40">
158
- <div class="text-[10px] uppercase tracking-wide text-ink-300 mb-1 px-2">Taxonomies</div>
159
- <button v-for="tk in taxonomyKeys" :key="tk"
160
- @click="activeTaxonomy = tk"
161
- class="w-full flex items-center gap-1.5 px-2 py-1 rounded-lg text-[11px] transition-colors"
162
- :class="activeTaxonomy === tk ? 'bg-ink-800/8 text-blue-700 font-medium' : 'text-ink-400 hover:bg-ink-50'"
163
- >
164
- <span class="w-3 text-ink-200">·</span>
165
- <span class="flex-1 text-left">{{ taxonomyLabels[tk] }}</span>
166
- </button>
179
+ <!-- SKOS Taxonomies -->
180
+ <div class="mt-2 pt-2 border-t border-ink-100/40">
181
+ <div class="text-[10px] uppercase tracking-wide text-ink-300 mb-1 px-2">Taxonomies</div>
182
+ <button v-for="tk in taxonomyKeys" :key="tk"
183
+ @click="selectTaxonomy(tk)"
184
+ class="w-full flex items-center gap-1.5 px-2 py-1 rounded-lg text-[11px] transition-colors"
185
+ :class="activeTaxonomy === tk ? 'bg-ink-800/8 text-blue-700 font-medium' : 'text-ink-400 hover:bg-ink-50'"
186
+ >
187
+ <span class="w-3 text-ink-200">·</span>
188
+ <span class="flex-1 text-left">{{ taxonomyLabels[tk] }}</span>
189
+ </button>
190
+ </div>
167
191
  </div>
168
- </nav>
169
- </div>
192
+ </template>
193
+ </nav>
170
194
 
171
195
  <!-- Dataset-level navigation (shown when viewing a dataset) -->
172
196
  <div v-if="showDatasetNav" class="mb-6">
@@ -6,9 +6,11 @@ import {
6
6
  type OwlClass,
7
7
  } from '../adapters/ontology-schema';
8
8
 
9
- const activeClassId = ref('gloss:Concept');
9
+ const OVERVIEW_ID = '__overview__';
10
+
11
+ const activeClassId = ref<string | null>(null);
10
12
  const activeTaxonomy = ref<string | null>(null);
11
- const expandedClasses = ref(new Set<string>(['gloss:Concept', 'gloss:Designation']));
13
+ const expandedClasses = ref(new Set<string>(['gloss:Designation']));
12
14
 
13
15
  const taxonomyKeys = [
14
16
  'conceptStatus', 'entryStatus', 'normativeStatus', 'sourceType', 'sourceStatus',
@@ -56,6 +58,8 @@ const supportingClasses = computed(() =>
56
58
  )
57
59
  );
58
60
 
61
+ const isOverview = computed(() => activeClassId.value === null && activeTaxonomy.value === null);
62
+
59
63
  const allNavItems = computed(() => {
60
64
  const items: { id: string; label: string; depth: number }[] = [];
61
65
  function walk(classes: OwlClass[], depth: number) {
@@ -80,6 +84,7 @@ export function useOntologyNav() {
80
84
  treeRoots,
81
85
  supportingClasses,
82
86
  allNavItems,
87
+ isOverview,
83
88
  toggleExpand,
84
89
  childClasses,
85
90
  hasChildren,
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
- import { computed } from 'vue';
3
- import { getClass, getAllPropertiesForClass, getStats } from '../adapters/ontology-schema';
2
+ import { computed, watch, nextTick } from 'vue';
3
+ import { getClass, getAllPropertiesForClass, getAllClasses, getStats } from '../adapters/ontology-schema';
4
4
  import { ontology, type TaxonomyConcept } from '../adapters/ontology-registry';
5
5
  import { useOntologyNav } from '../composables/use-ontology-nav';
6
6
 
@@ -11,10 +11,14 @@ const {
11
11
  activeTaxonomy,
12
12
  taxonomyLabels,
13
13
  allNavItems,
14
+ isOverview,
15
+ treeRoots,
16
+ hasChildren,
17
+ childClasses,
14
18
  } = useOntologyNav();
15
19
 
16
- const activeClass = computed(() => getClass(activeClassId.value));
17
- const activeProperties = computed(() => getAllPropertiesForClass(activeClassId.value));
20
+ const activeClass = computed(() => activeClassId.value ? getClass(activeClassId.value) : null);
21
+ const activeProperties = computed(() => activeClassId.value ? getAllPropertiesForClass(activeClassId.value) : { object: [], datatype: [] });
18
22
 
19
23
  function activeTaxonomyData() {
20
24
  if (!activeTaxonomy.value) return null;
@@ -23,151 +27,188 @@ function activeTaxonomyData() {
23
27
  const top = all.filter(c => !c.broader);
24
28
  return { scheme: ontology.getScheme(key), concepts: all, top };
25
29
  }
30
+
31
+ watch([activeClassId, activeTaxonomy], () => {
32
+ nextTick(() => {
33
+ const main = document.querySelector('main');
34
+ if (main) main.scrollTo({ top: 0 });
35
+ });
36
+ });
37
+
38
+ const allClasses = getAllClasses();
26
39
  </script>
27
40
 
28
41
  <template>
29
42
  <div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
30
- <!-- Header -->
31
- <div class="mb-8">
32
- <h1 class="text-2xl sm:text-3xl font-semibold text-ink-800">Glossarist Ontology</h1>
33
- <p class="text-sm text-ink-400 mt-1">
34
- OWL ontology for terminology management (ISO 10241-1, 30042, 12620, 25964/SKOS)
35
- </p>
36
- <div class="max-w-2xl mt-3 text-sm text-ink-500 leading-relaxed space-y-2">
37
- <p>The Glossarist ontology defines the RDF/OWL vocabulary for describing structured terminology data. It models <strong>concepts</strong> with multilingual <strong>localizations</strong> (definitions, notes, examples) and typed <strong>designations</strong> (expressions, abbreviations, symbols) using the SKOS-XL pattern for reified lexical labels.</p>
38
- <p>It aligns with <strong>SKOS</strong> (concepts and relationships), <strong>SKOS-XL</strong> (designations as labels), <strong>ISO 25964</strong> (hierarchical relationship subtypes — generic, partitive, instantial), <strong>PROV-O</strong> (source provenance), and <strong>Dublin Core Terms</strong> (language, citation). Enumeration values use SKOS ConceptSchemes (10 taxonomies).</p>
39
- </div>
40
- <div class="flex flex-wrap gap-2 mt-4">
41
- <span class="badge badge-blue text-[10px]">{{ stats.classCount }} classes</span>
42
- <span class="badge text-[10px] bg-emerald-50 text-emerald-700">{{ stats.objectPropertyCount }} object properties</span>
43
- <span class="badge text-[10px] bg-amber-50 text-amber-700">{{ stats.datatypePropertyCount }} datatype properties</span>
44
- <span class="badge text-[10px] bg-purple-50 text-purple-700">10 SKOS taxonomies</span>
43
+ <!-- Overview: header + class grid -->
44
+ <template v-if="isOverview">
45
+ <div class="mb-8">
46
+ <h1 class="text-2xl sm:text-3xl font-semibold text-ink-800">Glossarist Ontology</h1>
47
+ <p class="text-sm text-ink-400 mt-1">
48
+ OWL ontology for terminology management (ISO 10241-1, 30042, 12620, 25964/SKOS)
49
+ </p>
50
+ <div class="max-w-2xl mt-3 text-sm text-ink-500 leading-relaxed space-y-2">
51
+ <p>The Glossarist ontology defines the RDF/OWL vocabulary for describing structured terminology data. It models <strong>concepts</strong> with multilingual <strong>localizations</strong> (definitions, notes, examples) and typed <strong>designations</strong> (expressions, abbreviations, symbols) using the SKOS-XL pattern for reified lexical labels.</p>
52
+ <p>It aligns with <strong>SKOS</strong> (concepts and relationships), <strong>SKOS-XL</strong> (designations as labels), <strong>ISO 25964</strong> (hierarchical relationship subtypes — generic, partitive, instantial), <strong>PROV-O</strong> (source provenance), and <strong>Dublin Core Terms</strong> (language, citation). Enumeration values use SKOS ConceptSchemes (10 taxonomies).</p>
53
+ </div>
54
+ <div class="flex flex-wrap gap-2 mt-4">
55
+ <span class="badge badge-blue text-[10px]">{{ stats.classCount }} classes</span>
56
+ <span class="badge text-[10px] bg-emerald-50 text-emerald-700">{{ stats.objectPropertyCount }} object properties</span>
57
+ <span class="badge text-[10px] bg-amber-50 text-amber-700">{{ stats.datatypePropertyCount }} datatype properties</span>
58
+ <span class="badge text-[10px] bg-purple-50 text-purple-700">10 SKOS taxonomies</span>
59
+ </div>
60
+ <code class="block text-xs text-ink-400 mt-2">https://www.glossarist.org/ontologies/glossarist</code>
45
61
  </div>
46
- <code class="block text-xs text-ink-400 mt-2">https://www.glossarist.org/ontologies/glossarist</code>
47
- </div>
48
-
49
- <!-- Sticky mobile chips (for small screens where sidebar is hidden) -->
50
- <div class="lg:hidden sticky top-14 z-10 bg-surface -mx-4 px-4 py-2 border-b border-ink-100/60 mb-4">
51
- <div class="flex gap-2 overflow-x-auto scrollbar-none">
52
- <button v-for="item in allNavItems" :key="item.id"
53
- @click="activeClassId = item.id; activeTaxonomy = null"
54
- class="flex-shrink-0 px-3 py-2.5 rounded-lg text-xs font-medium whitespace-nowrap transition-colors min-h-[44px] flex items-center gap-1.5"
55
- :class="activeClassId === item.id && !activeTaxonomy
56
- ? 'bg-ink-800 text-white'
57
- : 'bg-surface-raised border border-ink-100 text-ink-600 hover:bg-ink-50'"
58
- >
59
- <span v-if="item.depth > 0" class="text-ink-300">{{ '&nbsp;'.repeat(item.depth * 2) }}</span>
60
- {{ item.label }}
61
- </button>
62
- <button @click="activeTaxonomy = activeTaxonomy ? null : 'conceptStatus'"
63
- class="flex-shrink-0 px-3 py-2.5 rounded-lg text-xs font-medium whitespace-nowrap transition-colors min-h-[44px] flex items-center gap-1.5"
64
- :class="activeTaxonomy
65
- ? 'bg-ink-800 text-white'
66
- : 'bg-surface-raised border border-ink-100 text-ink-600 hover:bg-ink-50'"
67
- >Taxonomies</button>
62
+
63
+ <!-- Sticky mobile chips (for small screens where sidebar is hidden) -->
64
+ <div class="lg:hidden sticky top-14 z-10 bg-surface -mx-4 px-4 py-2 border-b border-ink-100/60 mb-4">
65
+ <div class="flex gap-2 overflow-x-auto scrollbar-none">
66
+ <button v-for="item in allNavItems" :key="item.id"
67
+ @click="activeClassId = item.id; activeTaxonomy = null"
68
+ class="flex-shrink-0 px-3 py-2.5 rounded-lg text-xs font-medium whitespace-nowrap transition-colors min-h-[44px] flex items-center gap-1.5"
69
+ :class="activeClassId === item.id && !activeTaxonomy
70
+ ? 'bg-ink-800 text-white'
71
+ : 'bg-surface-raised border border-ink-100 text-ink-600 hover:bg-ink-50'"
72
+ >
73
+ {{ item.label }}
74
+ </button>
75
+ <button @click="activeTaxonomy = activeTaxonomy ? null : 'conceptStatus'"
76
+ class="flex-shrink-0 px-3 py-2.5 rounded-lg text-xs font-medium whitespace-nowrap transition-colors min-h-[44px] flex items-center gap-1.5"
77
+ :class="activeTaxonomy
78
+ ? 'bg-ink-800 text-white'
79
+ : 'bg-surface-raised border border-ink-100 text-ink-600 hover:bg-ink-50'"
80
+ >Taxonomies</button>
81
+ </div>
68
82
  </div>
69
- </div>
70
-
71
- <!-- Detail panel -->
72
- <div>
73
- <!-- Class detail -->
74
- <template v-if="!activeTaxonomy && activeClass">
75
- <div class="pb-4 border-b border-ink-100/60 mb-4">
76
- <h2 class="text-lg font-semibold text-ink-800">{{ activeClass.label }}</h2>
77
- <code class="block text-xs text-ink-400 mt-1">{{ activeClass.iri }}</code>
78
- <div v-if="activeClass.subClassOf" class="flex items-center gap-2 mt-2 text-sm">
79
- <span class="text-ink-400 text-xs">subClassOf</span>
80
- <code class="text-xs text-ink-600 bg-ink-50 px-2 py-0.5 rounded">{{ activeClass.subClassOf }}</code>
81
- <template v-if="activeClass.ancestors.length > 1">
82
- <span class="text-ink-300 text-xs">→</span>
83
- <code class="text-xs text-ink-500">{{ activeClass.ancestors.slice(1).join(' → ') }}</code>
84
- </template>
83
+
84
+ <h2 class="text-lg font-semibold text-ink-800 mb-4">Class Overview</h2>
85
+ <div class="grid gap-3 sm:grid-cols-2">
86
+ <div v-for="cls in allClasses" :key="cls.compact"
87
+ @click="activeClassId = cls.compact"
88
+ class="border border-ink-100/60 rounded-lg p-3 cursor-pointer hover:border-ink-200 hover:bg-ink-50/50 transition-colors">
89
+ <div class="flex items-center gap-2">
90
+ <span class="text-sm font-medium text-ink-700">{{ cls.label }}</span>
91
+ <code class="text-[10px] text-ink-400 bg-ink-50 px-1.5 py-0.5 rounded">{{ cls.compact }}</code>
92
+ </div>
93
+ <p v-if="cls.comment" class="text-xs text-ink-400 mt-1 line-clamp-2">{{ cls.comment }}</p>
94
+ <div v-if="cls.subClassOf" class="text-[10px] text-ink-300 mt-1">
95
+ subClassOf <code class="text-ink-400">{{ cls.subClassOf }}</code>
85
96
  </div>
86
- <p v-if="activeClass.comment" class="text-sm text-ink-500 mt-3 leading-relaxed">{{ activeClass.comment }}</p>
87
97
  </div>
98
+ </div>
99
+ </template>
88
100
 
89
- <!-- Object Properties -->
90
- <div v-if="activeProperties.object.length" class="mb-6">
91
- <h3 class="text-xs uppercase tracking-wide text-ink-300 font-medium mb-2">
92
- Object Properties ({{ activeProperties.object.length }})
93
- </h3>
94
- <table class="w-full text-sm border-collapse">
95
- <thead>
96
- <tr class="border-b border-ink-100/60">
97
- <th class="text-left text-[11px] font-medium text-ink-400 uppercase tracking-wide py-2 px-3">Property</th>
98
- <th class="text-left text-[11px] font-medium text-ink-400 uppercase tracking-wide py-2 px-3">Range</th>
99
- <th class="text-left text-[11px] font-medium text-ink-400 uppercase tracking-wide py-2 px-3">Description</th>
100
- </tr>
101
- </thead>
102
- <tbody>
103
- <tr v-for="p in activeProperties.object" :key="p.compact" class="border-b border-ink-100/30">
104
- <td class="py-2 px-3 align-top">
105
- <code class="text-xs text-blue-600 bg-blue-50 px-1.5 py-0.5 rounded">{{ p.compact }}</code>
106
- <div v-if="p.inverseOf" class="text-[10px] text-ink-300 mt-0.5">↔ {{ p.inverseOf }}</div>
107
- </td>
108
- <td class="py-2 px-3 align-top">
109
- <code class="text-xs text-ink-500">{{ p.range || p.rangeUnion?.join(' | ') || '—' }}</code>
110
- </td>
111
- <td class="py-2 px-3 text-xs text-ink-400 align-top">{{ p.comment || '' }}</td>
112
- </tr>
113
- </tbody>
114
- </table>
115
- </div>
101
+ <!-- Class detail (no overview header) -->
102
+ <template v-if="!activeTaxonomy && activeClass">
103
+ <!-- Breadcrumb -->
104
+ <nav class="flex items-center gap-1.5 text-sm text-ink-400 mb-4">
105
+ <button @click="activeClassId = null" class="hover:text-ink-700 transition-colors">Overview</button>
106
+ <span class="text-ink-200">/</span>
107
+ <span class="text-ink-700">{{ activeClass.label }}</span>
108
+ </nav>
116
109
 
117
- <!-- Datatype Properties -->
118
- <div v-if="activeProperties.datatype.length">
119
- <h3 class="text-xs uppercase tracking-wide text-ink-300 font-medium mb-2">
120
- Datatype Properties ({{ activeProperties.datatype.length }})
121
- </h3>
122
- <table class="w-full text-sm border-collapse">
123
- <thead>
124
- <tr class="border-b border-ink-100/60">
125
- <th class="text-left text-[11px] font-medium text-ink-400 uppercase tracking-wide py-2 px-3">Property</th>
126
- <th class="text-left text-[11px] font-medium text-ink-400 uppercase tracking-wide py-2 px-3">Datatype</th>
127
- <th class="text-left text-[11px] font-medium text-ink-400 uppercase tracking-wide py-2 px-3">Description</th>
128
- </tr>
129
- </thead>
130
- <tbody>
131
- <tr v-for="p in activeProperties.datatype" :key="p.compact" class="border-b border-ink-100/30">
132
- <td class="py-2 px-3 align-top">
133
- <code class="text-xs text-blue-600 bg-blue-50 px-1.5 py-0.5 rounded">{{ p.compact }}</code>
134
- </td>
135
- <td class="py-2 px-3 align-top">
136
- <code class="text-xs text-ink-500">{{ p.range || '—' }}</code>
137
- </td>
138
- <td class="py-2 px-3 text-xs text-ink-400 align-top">{{ p.comment || '' }}</td>
139
- </tr>
140
- </tbody>
141
- </table>
110
+ <div class="pb-4 border-b border-ink-100/60 mb-4">
111
+ <h1 class="text-xl font-semibold text-ink-800">{{ activeClass.label }}</h1>
112
+ <code class="block text-xs text-ink-400 mt-1">{{ activeClass.iri }}</code>
113
+ <div v-if="activeClass.subClassOf" class="flex items-center gap-2 mt-2 text-sm">
114
+ <span class="text-ink-400 text-xs">subClassOf</span>
115
+ <code class="text-xs text-ink-600 bg-ink-50 px-2 py-0.5 rounded">{{ activeClass.subClassOf }}</code>
116
+ <template v-if="activeClass.ancestors.length > 1">
117
+ <span class="text-ink-300 text-xs">→</span>
118
+ <code class="text-xs text-ink-500">{{ activeClass.ancestors.slice(1).join(' ') }}</code>
119
+ </template>
142
120
  </div>
121
+ <p v-if="activeClass.comment" class="text-sm text-ink-500 mt-3 leading-relaxed">{{ activeClass.comment }}</p>
122
+ </div>
143
123
 
144
- <div v-if="!activeProperties.object.length && !activeProperties.datatype.length" class="text-sm text-ink-300 italic">
145
- No properties defined directly on this class.
146
- </div>
147
- </template>
124
+ <!-- Object Properties -->
125
+ <div v-if="activeProperties.object.length" class="mb-6">
126
+ <h3 class="text-xs uppercase tracking-wide text-ink-300 font-medium mb-2">
127
+ Object Properties ({{ activeProperties.object.length }})
128
+ </h3>
129
+ <table class="w-full text-sm border-collapse">
130
+ <thead>
131
+ <tr class="border-b border-ink-100/60">
132
+ <th class="text-left text-[11px] font-medium text-ink-400 uppercase tracking-wide py-2 px-3">Property</th>
133
+ <th class="text-left text-[11px] font-medium text-ink-400 uppercase tracking-wide py-2 px-3">Range</th>
134
+ <th class="text-left text-[11px] font-medium text-ink-400 uppercase tracking-wide py-2 px-3">Description</th>
135
+ </tr>
136
+ </thead>
137
+ <tbody>
138
+ <tr v-for="p in activeProperties.object" :key="p.compact" class="border-b border-ink-100/30">
139
+ <td class="py-2 px-3 align-top">
140
+ <code class="text-xs text-blue-600 bg-blue-50 px-1.5 py-0.5 rounded">{{ p.compact }}</code>
141
+ <div v-if="p.inverseOf" class="text-[10px] text-ink-300 mt-0.5">↔ {{ p.inverseOf }}</div>
142
+ </td>
143
+ <td class="py-2 px-3 align-top">
144
+ <code class="text-xs text-ink-500">{{ p.range || p.rangeUnion?.join(' | ') || '—' }}</code>
145
+ </td>
146
+ <td class="py-2 px-3 text-xs text-ink-400 align-top">{{ p.comment || '' }}</td>
147
+ </tr>
148
+ </tbody>
149
+ </table>
150
+ </div>
148
151
 
149
- <!-- Taxonomy detail -->
150
- <template v-if="activeTaxonomy && activeTaxonomyData()">
151
- <div class="pb-4 border-b border-ink-100/60 mb-4">
152
- <h2 class="text-lg font-semibold text-ink-800">{{ taxonomyLabels[activeTaxonomy] }}</h2>
153
- <code class="block text-xs text-ink-400 mt-1">{{ activeTaxonomyData()!.scheme }}</code>
154
- </div>
152
+ <!-- Datatype Properties -->
153
+ <div v-if="activeProperties.datatype.length">
154
+ <h3 class="text-xs uppercase tracking-wide text-ink-300 font-medium mb-2">
155
+ Datatype Properties ({{ activeProperties.datatype.length }})
156
+ </h3>
157
+ <table class="w-full text-sm border-collapse">
158
+ <thead>
159
+ <tr class="border-b border-ink-100/60">
160
+ <th class="text-left text-[11px] font-medium text-ink-400 uppercase tracking-wide py-2 px-3">Property</th>
161
+ <th class="text-left text-[11px] font-medium text-ink-400 uppercase tracking-wide py-2 px-3">Datatype</th>
162
+ <th class="text-left text-[11px] font-medium text-ink-400 uppercase tracking-wide py-2 px-3">Description</th>
163
+ </tr>
164
+ </thead>
165
+ <tbody>
166
+ <tr v-for="p in activeProperties.datatype" :key="p.compact" class="border-b border-ink-100/30">
167
+ <td class="py-2 px-3 align-top">
168
+ <code class="text-xs text-blue-600 bg-blue-50 px-1.5 py-0.5 rounded">{{ p.compact }}</code>
169
+ </td>
170
+ <td class="py-2 px-3 align-top">
171
+ <code class="text-xs text-ink-500">{{ p.range || '—' }}</code>
172
+ </td>
173
+ <td class="py-2 px-3 text-xs text-ink-400 align-top">{{ p.comment || '' }}</td>
174
+ </tr>
175
+ </tbody>
176
+ </table>
177
+ </div>
178
+
179
+ <div v-if="!activeProperties.object.length && !activeProperties.datatype.length" class="text-sm text-ink-300 italic">
180
+ No properties defined directly on this class.
181
+ </div>
182
+ </template>
155
183
 
156
- <div class="space-y-2">
157
- <div v-for="concept in activeTaxonomyData()!.concepts" :key="concept.id"
158
- class="border border-ink-100/60 rounded-lg p-3">
159
- <div class="flex items-center gap-2">
160
- <code class="text-xs font-semibold text-ink-700">{{ concept.id }}</code>
161
- <span class="text-sm text-ink-600">{{ concept.prefLabel }}</span>
162
- <span v-if="concept.altLabel" class="text-xs text-ink-400">({{ concept.altLabel }})</span>
163
- </div>
164
- <p v-if="concept.definition" class="text-xs text-ink-400 mt-1 leading-relaxed">{{ concept.definition }}</p>
165
- <div v-if="concept.broader" class="text-[10px] text-ink-300 mt-1">
166
- broader: <code class="text-ink-400">{{ concept.broader }}</code>
167
- </div>
184
+ <!-- Taxonomy detail (no overview header) -->
185
+ <template v-if="activeTaxonomy && activeTaxonomyData()">
186
+ <!-- Breadcrumb -->
187
+ <nav class="flex items-center gap-1.5 text-sm text-ink-400 mb-4">
188
+ <button @click="activeTaxonomy = null; activeClassId = null" class="hover:text-ink-700 transition-colors">Overview</button>
189
+ <span class="text-ink-200">/</span>
190
+ <span class="text-ink-700">{{ taxonomyLabels[activeTaxonomy] }}</span>
191
+ </nav>
192
+
193
+ <div class="pb-4 border-b border-ink-100/60 mb-4">
194
+ <h1 class="text-xl font-semibold text-ink-800">{{ taxonomyLabels[activeTaxonomy] }}</h1>
195
+ <code class="block text-xs text-ink-400 mt-1">{{ activeTaxonomyData()!.scheme }}</code>
196
+ </div>
197
+
198
+ <div class="space-y-2">
199
+ <div v-for="concept in activeTaxonomyData()!.concepts" :key="concept.id"
200
+ class="border border-ink-100/60 rounded-lg p-3">
201
+ <div class="flex items-center gap-2">
202
+ <code class="text-xs font-semibold text-ink-700">{{ concept.id }}</code>
203
+ <span class="text-sm text-ink-600">{{ concept.prefLabel }}</span>
204
+ <span v-if="concept.altLabel" class="text-xs text-ink-400">({{ concept.altLabel }})</span>
205
+ </div>
206
+ <p v-if="concept.definition" class="text-xs text-ink-400 mt-1 leading-relaxed">{{ concept.definition }}</p>
207
+ <div v-if="concept.broader" class="text-[10px] text-ink-300 mt-1">
208
+ broader: <code class="text-ink-400">{{ concept.broader }}</code>
168
209
  </div>
169
210
  </div>
170
- </template>
171
- </div>
211
+ </div>
212
+ </template>
172
213
  </div>
173
214
  </template>