@pure-ds/storybook 0.1.4 → 0.1.6

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.
@@ -0,0 +1,3621 @@
1
+ import { LitElement, html, nothing, render, unsafeHTML } from "../../../src/js/lit.js";
2
+ import { PDS } from "../../../src/js/pds.js";
3
+
4
+ import { AutoComplete } from "pure-web/ac";
5
+
6
+ const toast = (message, options) => {
7
+ const toaster = document.getElementById("global-toaster");
8
+ toaster.toast(message, options);
9
+ };
10
+
11
+ customElements.define(
12
+ "pds-demo",
13
+ class extends LitElement {
14
+ #shiki = null;
15
+ #shikiLoading = false;
16
+
17
+ static properties = {
18
+ config: { type: Object },
19
+ designer: { type: Object },
20
+ sections: { type: Array, state: true },
21
+ inspectorActive: { type: Boolean, state: true },
22
+ };
23
+
24
+ constructor() {
25
+ super();
26
+ this.config = null;
27
+ this.designer = null;
28
+ this.sections = [];
29
+ this.inspectorActive = false;
30
+ this._docsBase = "/pds";
31
+ // Showdown (Markdown) converter will be loaded from CDN on demand
32
+ this._showdown = null;
33
+ this._showdownLoading = false;
34
+ }
35
+
36
+ // Disable shadow DOM to use global styles
37
+ createRenderRoot() {
38
+ return this;
39
+ }
40
+
41
+ connectedCallback() {
42
+ super.connectedCallback();
43
+
44
+ // Listen for design updates from unified PDS bus
45
+ PDS.addEventListener("pds:design:updated", (e) => {
46
+ this.config = e.detail.config;
47
+ this.designer = e.detail.designer;
48
+ // Update docs base if staticBase changes
49
+ if (this.config && this.config.staticBase) {
50
+ this._docsBase =
51
+ "/" + String(this.config.staticBase).replace(/^\/+|\/+$/g, "");
52
+ }
53
+ });
54
+
55
+ // Listen for field changes to scroll to relevant section
56
+ PDS.addEventListener("pds:design:field:changed", (e) => {
57
+ setTimeout(() => {
58
+ this.scrollToRelevantSection(e.detail.field);
59
+ }, 1000);
60
+ });
61
+
62
+ // Listen for inspector mode changes
63
+ PDS.addEventListener("pds:inspector:mode:changed", (e) => {
64
+ this.inspectorActive = e.detail.active;
65
+ });
66
+
67
+ // Extract sections after initial render
68
+ setTimeout(() => {
69
+ this.extractSections();
70
+ this.handleInitialHash();
71
+ }, 100);
72
+
73
+ // Capture-phase handler to prevent interactive actions when inspector is active
74
+ this._inspectorCaptureHandler = (e) => {
75
+ if (!this.inspectorActive) return;
76
+ const target = e.target;
77
+
78
+ // Prevent link navigation
79
+ const anchor = target.closest && target.closest("a[href]");
80
+ if (anchor) {
81
+ e.preventDefault();
82
+ return;
83
+ }
84
+
85
+ // Prevent button activation
86
+ const button = target.closest && target.closest("button");
87
+ if (button) {
88
+ e.preventDefault();
89
+ return;
90
+ }
91
+ };
92
+
93
+ this.addEventListener("click", this._inspectorCaptureHandler, true);
94
+ // Determine docs base from global override or config
95
+ try {
96
+ const globalBase = window.PDS_DOCS_BASE;
97
+ if (typeof globalBase === "string" && globalBase.trim()) {
98
+ this._docsBase = globalBase.replace(/\/+$/, "");
99
+ }
100
+ } catch {}
101
+ // Defer to config.staticBase if provided
102
+ if (this.config && this.config.staticBase) {
103
+ this._docsBase =
104
+ "/" + String(this.config.staticBase).replace(/^\/+|\/+$/g, "");
105
+ }
106
+
107
+ // Listen for external requests to view docs via PDS bus
108
+ PDS.addEventListener("pds:docs:view", async (e) => {
109
+ const file = (e.detail && e.detail.file) || "README.md";
110
+ await this._renderDocToDialog(file);
111
+ });
112
+ }
113
+
114
+ disconnectedCallback() {
115
+ super.disconnectedCallback();
116
+ if (this._inspectorCaptureHandler) {
117
+ this.removeEventListener("click", this._inspectorCaptureHandler, true);
118
+ this._inspectorCaptureHandler = null;
119
+ }
120
+ }
121
+
122
+ /** Fetch a markdown file from the docs base and return HTML */
123
+ async fetchDocHTML(file = "README.md") {
124
+ const base = this._docsBase || "/pds";
125
+ const url = `${base.replace(/\/+$/, "")}/${file}`;
126
+ try {
127
+ const res = await fetch(url, { cache: "no-cache" });
128
+ if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
129
+ const md = await res.text();
130
+ const conv = await this.getShowdownConverter();
131
+ return conv ? conv.makeHtml(md) : `<pre>${this.escapeHTML(md)}</pre>`;
132
+ } catch (err) {
133
+ return `<p>Failed to load ${file} from ${base}: ${String(
134
+ err.message || err
135
+ )}</p>`;
136
+ }
137
+ }
138
+
139
+ /** Render markdown into a simple dialog overlay */
140
+ async _renderDocToDialog(file) {
141
+ const htmlContent = await this.fetchDocHTML(file);
142
+ let dlg = document.getElementById("pds-docs-dialog");
143
+ if (!dlg) {
144
+ dlg = document.createElement("dialog");
145
+ dlg.id = "pds-docs-dialog";
146
+ dlg.style.width = "min(900px, 90vw)";
147
+ dlg.style.maxHeight = "85vh";
148
+ dlg.style.padding = "0";
149
+ dlg.innerHTML = `<div style="padding:16px 20px; overflow:auto; max-height:85vh">
150
+ <div class="markdown-body"></div>
151
+ </div>`;
152
+ document.body.appendChild(dlg);
153
+ }
154
+ const body = dlg.querySelector(".markdown-body");
155
+ if (body) body.innerHTML = htmlContent;
156
+ if (!dlg.open) dlg.showModal();
157
+ }
158
+
159
+ deactivateInspector() {
160
+ // Dispatch request on PDS bus to toggle inspector mode off
161
+ PDS.dispatchEvent(
162
+ new CustomEvent("pds:inspector:deactivate", {
163
+ detail: {},
164
+ })
165
+ );
166
+ }
167
+
168
+ extractSections() {
169
+ const sectionElements = this.querySelectorAll("[data-section]");
170
+ this.sections = Array.from(sectionElements).map((el) => {
171
+ const id = el.getAttribute("data-section");
172
+ const heading = el.querySelector("h2");
173
+ const title = heading?.textContent?.trim() || id;
174
+ return { id, title };
175
+ });
176
+ }
177
+
178
+ handleInitialHash() {
179
+ const hash = window.location.hash.slice(1);
180
+ if (hash) {
181
+ const section = this.querySelector(`[data-section="${hash}"]`);
182
+ if (section) {
183
+ setTimeout(() => {
184
+ section.scrollIntoView({ behavior: "smooth", block: "start" });
185
+ }, 300);
186
+ }
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Smart element detection for code inspector
192
+ * Returns the appropriate element and metadata to display
193
+ */
194
+ detectComponentElement(clickedElement) {
195
+ let element = clickedElement;
196
+ let componentType = "element";
197
+ let displayName = element.tagName.toLowerCase();
198
+
199
+ // Skip if clicked on TOC
200
+ if (element.closest(".showcase-toc")) {
201
+ return null;
202
+ }
203
+
204
+ // Never select pds-demo itself
205
+ if (element.tagName === "DS-SHOWCASE") {
206
+ return null;
207
+ }
208
+
209
+ // Never select showcase-section - find component within it
210
+ if (
211
+ element.classList.contains("showcase-section") ||
212
+ element.closest(".showcase-section") === element
213
+ ) {
214
+ return null;
215
+ }
216
+
217
+ // Check for progressive enhancements (nav[data-dropdown], label[data-toggle], etc.)
218
+ const enhancedElement = this.findEnhancedElement(element);
219
+ if (enhancedElement) {
220
+ return {
221
+ element: enhancedElement,
222
+ componentType: "enhanced-component",
223
+ displayName: this.getEnhancedElementName(enhancedElement),
224
+ };
225
+ }
226
+
227
+ // Prioritize semantic HTML primitives (figure, table, details, etc.)
228
+ const semanticElements = [
229
+ "FIGURE",
230
+ "TABLE",
231
+ "DETAILS",
232
+ "VIDEO",
233
+ "AUDIO",
234
+ "PICTURE",
235
+ "BLOCKQUOTE",
236
+ "PRE",
237
+ "CODE",
238
+ ];
239
+ if (semanticElements.includes(element.tagName)) {
240
+ return {
241
+ element: element,
242
+ componentType: "html-primitive",
243
+ displayName: element.tagName.toLowerCase(),
244
+ };
245
+ }
246
+
247
+ // Check if inside a semantic HTML element
248
+ for (const tag of semanticElements) {
249
+ const semanticParent = element.closest(tag.toLowerCase());
250
+ if (semanticParent) {
251
+ return {
252
+ element: semanticParent,
253
+ componentType: "html-primitive",
254
+ displayName: tag.toLowerCase(),
255
+ };
256
+ }
257
+ }
258
+
259
+ // Check for PDS-styled primitives (elements styled by PDS classes)
260
+ const pdsStyledElement = this.findPDSStyledElement(element);
261
+ if (pdsStyledElement) {
262
+ return pdsStyledElement;
263
+ }
264
+
265
+ // Fieldset with role="group" or role="radiogroup" is a component
266
+ if (element.tagName === "FIELDSET") {
267
+ const role = element.getAttribute("role");
268
+ if (role === "group" || role === "radiogroup") {
269
+ componentType = "form-group";
270
+ displayName = role === "radiogroup" ? "radio group" : "form group";
271
+ return { element, componentType, displayName };
272
+ }
273
+ }
274
+
275
+ // Check if clicked element is inside a fieldset with role
276
+ const fieldsetParent = element.closest(
277
+ 'fieldset[role="group"], fieldset[role="radiogroup"]'
278
+ );
279
+ if (fieldsetParent) {
280
+ const role = fieldsetParent.getAttribute("role");
281
+ return {
282
+ element: fieldsetParent,
283
+ componentType: "form-group",
284
+ displayName: role === "radiogroup" ? "radio group" : "form group",
285
+ };
286
+ }
287
+
288
+ // Label with ANY input is always a component
289
+ if (element.tagName === "LABEL" || element.closest("label")) {
290
+ const label =
291
+ element.tagName === "LABEL" ? element : element.closest("label");
292
+ const input = label.querySelector("input, select, textarea");
293
+ if (input) {
294
+ componentType = "form-control";
295
+ displayName = `${input.tagName.toLowerCase()} field`;
296
+ return { element: label, componentType, displayName };
297
+ }
298
+ }
299
+
300
+ // Form elements - get the label container (if exists)
301
+ if (["INPUT", "SELECT", "TEXTAREA"].includes(element.tagName)) {
302
+ const label = element.closest("label");
303
+ if (label) {
304
+ element = label;
305
+ componentType = "form-control";
306
+ displayName = `${clickedElement.tagName.toLowerCase()} field`;
307
+ return { element, componentType, displayName };
308
+ }
309
+ // If no label, return the form element itself
310
+ componentType = "form-control";
311
+ displayName = `${element.tagName.toLowerCase()}`;
312
+ return { element, componentType, displayName };
313
+ }
314
+
315
+ // Custom web components (pds-* or other custom elements with hyphen)
316
+ if (element.tagName.includes("-")) {
317
+ componentType = "web-component";
318
+ displayName = element.tagName.toLowerCase();
319
+ return { element, componentType, displayName };
320
+ }
321
+
322
+ // Check if inside a custom web component
323
+ const customParent = element.closest("[tagName*='-']");
324
+ if (customParent && customParent.tagName.includes("-")) {
325
+ return {
326
+ element: customParent,
327
+ componentType: "web-component",
328
+ displayName: customParent.tagName.toLowerCase(),
329
+ };
330
+ }
331
+
332
+ // Buttons with icons
333
+ if (element.tagName === "BUTTON" || element.closest("button")) {
334
+ element =
335
+ element.tagName === "BUTTON" ? element : element.closest("button");
336
+ componentType = "button";
337
+ const hasIcon = element.querySelector("pds-icon");
338
+ displayName = hasIcon ? "button with icon" : "button";
339
+ return { element, componentType, displayName };
340
+ }
341
+
342
+ // SVG icons
343
+ if (element.tagName === "pds-icon" || element.closest("pds-icon")) {
344
+ element =
345
+ element.tagName === "pds-icon"
346
+ ? element
347
+ : element.closest("pds-icon");
348
+ componentType = "icon";
349
+ displayName = `pds-icon (${element.getAttribute("icon") || "unknown"})`;
350
+ return { element, componentType, displayName };
351
+ }
352
+
353
+ // Navigation elements
354
+ if (element.tagName === "NAV" || element.closest("nav[data-dropdown]")) {
355
+ element = element.closest("nav[data-dropdown]") || element;
356
+ componentType = "navigation";
357
+ displayName = "dropdown menu";
358
+ return { element, componentType, displayName };
359
+ }
360
+
361
+ // Generic container with interesting classes (but not showcase-section)
362
+ const interestingClasses = [
363
+ "color-card",
364
+ "color-scale",
365
+ "grid",
366
+ "flex-wrap",
367
+ "btn-group",
368
+ ];
369
+
370
+ for (const cls of interestingClasses) {
371
+ if (element.classList.contains(cls)) {
372
+ componentType = "container";
373
+ displayName = cls.replace(/-/g, " ");
374
+ return { element, componentType, displayName };
375
+ }
376
+ const container = element.closest(`.${cls}`);
377
+ if (container) {
378
+ element = container;
379
+ componentType = "container";
380
+ displayName = cls.replace(/-/g, " ");
381
+ return { element, componentType, displayName };
382
+ }
383
+ }
384
+
385
+ // Find nearest meaningful parent (not showcase-section)
386
+ const meaningfulParent = this.findNearestComponent(element);
387
+ if (meaningfulParent && meaningfulParent !== element) {
388
+ return this.detectComponentElement(meaningfulParent);
389
+ }
390
+
391
+ return { element, componentType, displayName };
392
+ }
393
+
394
+ /**
395
+ * Find PDS-styled elements (primitives styled by PDS classes or semantic HTML)
396
+ */
397
+ findPDSStyledElement(element) {
398
+ // Delegate to ontology-driven lookup
399
+ const res = PDS.findComponentForElement(element, { maxDepth: 5 });
400
+ if (!res) return null;
401
+ if (res.element && res.element.tagName === "DS-SHOWCASE") return null;
402
+ return {
403
+ element: res.element,
404
+ componentType: res.componentType || "pds-primitive",
405
+ displayName:
406
+ res.displayName || (res.element?.tagName || "element").toLowerCase(),
407
+ };
408
+ }
409
+
410
+ /**
411
+ * Find enhanced elements (progressive enhancements from config)
412
+ */
413
+ findEnhancedElement(element) {
414
+ // Check common enhancement patterns
415
+ const enhancementSelectors = [
416
+ "nav[data-dropdown]",
417
+ "label[data-toggle]",
418
+ "[data-tabs]",
419
+ "[data-modal]",
420
+ "[data-tooltip]",
421
+ ];
422
+
423
+ for (const selector of enhancementSelectors) {
424
+ if (element.matches && element.matches(selector)) {
425
+ return element;
426
+ }
427
+ const enhanced = element.closest(selector);
428
+ if (enhanced) {
429
+ return enhanced;
430
+ }
431
+ }
432
+
433
+ return null;
434
+ }
435
+
436
+ /**
437
+ * Get descriptive name for enhanced element
438
+ */
439
+ getEnhancedElementName(element) {
440
+ if (element.matches("nav[data-dropdown]")) return "dropdown menu";
441
+ if (element.matches("label[data-toggle]")) return "toggle switch";
442
+ if (element.matches("[data-tabs]")) return "tab component";
443
+ if (element.matches("[data-modal]")) return "modal dialog";
444
+ if (element.matches("[data-tooltip]")) return "tooltip";
445
+
446
+ // Fallback
447
+ const dataAttrs = Array.from(element.attributes)
448
+ .filter((attr) => attr.name.startsWith("data-"))
449
+ .map((attr) => attr.name.replace("data-", ""));
450
+
451
+ return dataAttrs[0]
452
+ ? `${dataAttrs[0]} component`
453
+ : element.tagName.toLowerCase();
454
+ }
455
+
456
+ /**
457
+ * Find nearest meaningful component (not showcase-section, pds-demo, or generic divs)
458
+ * Maximum 5 levels up to prevent going too high
459
+ */
460
+ findNearestComponent(element) {
461
+ let current = element.parentElement;
462
+ let level = 0;
463
+ const maxLevels = 5;
464
+
465
+ while (current && level < maxLevels) {
466
+ level++;
467
+
468
+ // Never traverse beyond pds-demo - it's too high, return null
469
+ if (current.tagName === "DS-SHOWCASE") {
470
+ return null;
471
+ }
472
+
473
+ // Skip showcase-section but continue traversing
474
+ if (current.classList.contains("showcase-section")) {
475
+ current = current.parentElement;
476
+ continue;
477
+ }
478
+
479
+ // Check if this is a meaningful component
480
+ if (
481
+ current.tagName.includes("-") || // Custom element
482
+ current.tagName === "BUTTON" ||
483
+ current.tagName === "NAV" ||
484
+ current.tagName === "FIELDSET" ||
485
+ current.tagName === "LABEL" ||
486
+ current.tagName === "TABLE" ||
487
+ current.tagName === "FIGURE" ||
488
+ current.tagName === "BLOCKQUOTE" ||
489
+ current.tagName === "ARTICLE" ||
490
+ current.hasAttribute("role") ||
491
+ current.hasAttribute("data-dropdown") ||
492
+ current.hasAttribute("data-toggle") ||
493
+ this.hasPDSClass(current)
494
+ ) {
495
+ return current;
496
+ }
497
+
498
+ current = current.parentElement;
499
+ }
500
+
501
+ return null;
502
+ }
503
+
504
+ /**
505
+ * Check if element has any PDS component/primitive class
506
+ */
507
+ hasPDSClass(element) {
508
+ const pdsClassPrefixes = [
509
+ "card",
510
+ "surface",
511
+ "tag",
512
+ "badge",
513
+ "pill",
514
+ "chip",
515
+ "alert",
516
+ "toast",
517
+ "notification",
518
+ "message",
519
+ "accordion",
520
+ "collapse",
521
+ "expandable",
522
+ "list-group",
523
+ "menu-list",
524
+ "nav-list",
525
+ "breadcrumb",
526
+ "pagination",
527
+ "tabs",
528
+ "tab",
529
+ "progress",
530
+ "spinner",
531
+ "loader",
532
+ "skeleton",
533
+ "divider",
534
+ "separator",
535
+ "avatar",
536
+ "thumbnail",
537
+ "form-group",
538
+ "input-group",
539
+ "checkbox-group",
540
+ "radio-group",
541
+ "btn-",
542
+ "icon-only",
543
+ "dropdown",
544
+ "popover",
545
+ "tooltip",
546
+ "modal",
547
+ "dialog",
548
+ "table-responsive",
549
+ "data-table",
550
+ "card-grid",
551
+ "media-object",
552
+ "status",
553
+ "indicator",
554
+ "dot-indicator",
555
+ "pulse",
556
+ ];
557
+
558
+ return Array.from(element.classList).some((cls) =>
559
+ pdsClassPrefixes.some((prefix) => cls.startsWith(prefix))
560
+ );
561
+ }
562
+
563
+ /**
564
+ * Extract HTML with smart formatting
565
+ */
566
+ extractHTML(element) {
567
+ const clone = element.cloneNode(true);
568
+
569
+ // Remove event listeners and internal state
570
+ const allElements = [clone, ...clone.querySelectorAll("*")];
571
+ allElements.forEach((el) => {
572
+ // Remove Lit internal attributes
573
+ if (el.removeAttribute) {
574
+ el.removeAttribute("_$litPart$");
575
+ el.removeAttribute("_$litElement$");
576
+ }
577
+ });
578
+
579
+ // Pretty print HTML using DOM-aware formatter
580
+ let html = this.formatHTMLElement(clone);
581
+
582
+ // Extract Lit properties (attributes starting with .)
583
+ const litProps = this.extractLitProperties(element);
584
+
585
+ return { html, litProps };
586
+ }
587
+
588
+ /**
589
+ * Extract Lit property bindings from element
590
+ */
591
+ extractLitProperties(element) {
592
+ const props = [];
593
+
594
+ // Check for common Lit property patterns
595
+ if (element.checked !== undefined && element.type === "checkbox") {
596
+ props.push({ name: ".checked", value: element.checked });
597
+ }
598
+
599
+ if (
600
+ element.value !== undefined &&
601
+ ["INPUT", "SELECT", "TEXTAREA"].includes(element.tagName)
602
+ ) {
603
+ props.push({ name: ".value", value: element.value });
604
+ }
605
+
606
+ // Check for custom element properties
607
+ if (element.tagName.includes("-")) {
608
+ const constructor = customElements.get(element.tagName.toLowerCase());
609
+ if (constructor && constructor.properties) {
610
+ Object.keys(constructor.properties).forEach((prop) => {
611
+ if (
612
+ element[prop] !== undefined &&
613
+ typeof element[prop] !== "function"
614
+ ) {
615
+ props.push({ name: `.${prop}`, value: element[prop] });
616
+ }
617
+ });
618
+ }
619
+ }
620
+
621
+ return props;
622
+ }
623
+
624
+ /**
625
+ * Format HTML with indentation
626
+ */
627
+ formatHTMLElement(node, level = 0) {
628
+ const indent = (n) => " ".repeat(n);
629
+ let out = "";
630
+
631
+ const escapeText = (s) =>
632
+ s
633
+ .replace(/&/g, "&amp;")
634
+ .replace(/</g, "&lt;")
635
+ .replace(/>/g, "&gt;")
636
+ .replace(/"/g, "&quot;")
637
+ .replace(/'/g, "&#039;");
638
+
639
+ const isVoid = (tag) =>
640
+ [
641
+ "area",
642
+ "base",
643
+ "br",
644
+ "col",
645
+ "embed",
646
+ "hr",
647
+ "img",
648
+ "input",
649
+ "link",
650
+ "meta",
651
+ "param",
652
+ "source",
653
+ "track",
654
+ "wbr",
655
+ ].includes(tag);
656
+
657
+ if (node.nodeType === Node.TEXT_NODE) {
658
+ const t = node.textContent.replace(/\s+/g, " ").trim();
659
+ if (t.length === 0) return "";
660
+ return indent(level) + escapeText(t) + "\n";
661
+ }
662
+
663
+ if (node.nodeType === Node.COMMENT_NODE) {
664
+ return indent(level) + `<!-- ${escapeText(node.textContent)} -->\n`;
665
+ }
666
+
667
+ if (node.nodeType === Node.ELEMENT_NODE) {
668
+ const tag = node.tagName.toLowerCase();
669
+ const attrs = [];
670
+ const booleanDataAttrs = new Set(["data-label", "data-toggle"]);
671
+ for (const a of node.attributes) {
672
+ const name = a.name;
673
+ const val = a.value;
674
+
675
+ // Special-case certain data-* attributes as boolean markers
676
+ if (booleanDataAttrs.has(name)) {
677
+ // Render as bare boolean attribute (e.g., data-toggle)
678
+ attrs.push(name);
679
+ continue;
680
+ }
681
+
682
+ if (val === "") {
683
+ // Render empty data-* attributes explicitly as data-xxx=""
684
+ if (name.startsWith("data-")) {
685
+ attrs.push(`${name}=""`);
686
+ } else {
687
+ // For other boolean-ish attributes, render bare attribute
688
+ attrs.push(name);
689
+ }
690
+ } else {
691
+ attrs.push(`${name}="${escapeText(val)}"`);
692
+ }
693
+ }
694
+ const attrString = attrs.length ? " " + attrs.join(" ") : "";
695
+
696
+ // Start tag
697
+ out += indent(level) + `<${tag}${attrString}>` + "\n";
698
+
699
+ // Children
700
+ for (const child of Array.from(node.childNodes)) {
701
+ out += this.formatHTMLElement(child, level + 1);
702
+ }
703
+
704
+ // End tag (void elements don't need explicit closing)
705
+ if (!isVoid(tag)) {
706
+ out += indent(level) + `</${tag}>` + "\n";
707
+ }
708
+
709
+ return out;
710
+ }
711
+
712
+ return "";
713
+ }
714
+
715
+ /**
716
+ * Handle click in inspector mode
717
+ */
718
+ handleInspectorClick(e) {
719
+ if (!this.inspectorActive) return;
720
+
721
+ e.preventDefault();
722
+ e.stopPropagation();
723
+
724
+ const detected = this.detectComponentElement(e.target);
725
+ if (!detected) return;
726
+
727
+ const { element, componentType, displayName } = detected;
728
+
729
+ // Turn off inspector mode after selecting an element (like a color picker)
730
+ this.deactivateInspector();
731
+
732
+ // Check if an enhancer provides a demo HTML to display (clean template)
733
+ let demoHtml = null;
734
+ let enhancer = null;
735
+ try {
736
+ const enhancers =
737
+ this.config?.autoDefine?.enhancers ||
738
+ (typeof appConfig !== "undefined"
739
+ ? appConfig?.autoDefine?.enhancers
740
+ : null) ||
741
+ (typeof window !== "undefined"
742
+ ? window?.appConfig?.autoDefine?.enhancers
743
+ : null) ||
744
+ (document.querySelector &&
745
+ document.querySelector("pure-app")?.config?.autoDefine
746
+ ?.enhancers) ||
747
+ [];
748
+ enhancer = enhancers.find((en) => {
749
+ try {
750
+ return (
751
+ (element.matches && element.matches(en.selector)) ||
752
+ (element.closest && element.closest(en.selector))
753
+ );
754
+ } catch (ex) {
755
+ return false;
756
+ }
757
+ });
758
+ } catch (ex) {
759
+ enhancer = null;
760
+ }
761
+
762
+ let litProps = [];
763
+ let html = null;
764
+ if (enhancer && enhancer.demoHtml) {
765
+ demoHtml =
766
+ typeof enhancer.demoHtml === "function"
767
+ ? enhancer.demoHtml(element)
768
+ : enhancer.demoHtml;
769
+ // If demoHtml is a string, parse it into a DOM node and pretty-print it
770
+ if (typeof demoHtml === "string") {
771
+ try {
772
+ const wrapper = document.createElement("div");
773
+ wrapper.innerHTML = demoHtml.trim();
774
+ // If there are multiple top-level nodes, format them all
775
+ let combined = "";
776
+ for (const child of Array.from(wrapper.childNodes)) {
777
+ combined += this.formatHTMLElement(child);
778
+ }
779
+ html = combined;
780
+ } catch (ex) {
781
+ // Fallback to raw string if parsing fails
782
+ html = demoHtml;
783
+ }
784
+ } else {
785
+ // If the enhancer returned a DOM node or other structure, try to format it
786
+ if (demoHtml instanceof Node) {
787
+ html = this.formatHTMLElement(demoHtml);
788
+ } else {
789
+ html = String(demoHtml);
790
+ }
791
+ }
792
+ // still extract lit props from real element if useful
793
+ litProps = this.extractLitProperties(element);
794
+ } else {
795
+ const res = this.extractHTML(element);
796
+ html = res.html;
797
+ litProps = res.litProps;
798
+ }
799
+
800
+ // Show code in drawer
801
+ this.showCodeDrawer(html, litProps, displayName, componentType);
802
+ }
803
+
804
+ /**
805
+ * Show code in pds-drawer
806
+ */
807
+ async showCodeDrawer(htmlCode, litProps, displayName, componentType) {
808
+ const drawer = document.querySelector("#global-drawer");
809
+
810
+ // Create header template
811
+ const headerTemplate = html`
812
+ <div class="code-drawer-header">
813
+ <div class="code-drawer-title">
814
+ <pds-icon icon="code" size="sm"></pds-icon>
815
+ <span>${displayName}</span>
816
+ <span class="component-type-badge">${componentType}</span>
817
+ </div>
818
+ <button class="copy-code-btn" id="copyCodeBtn">
819
+ <pds-icon icon="clipboard" size="sm"></pds-icon>
820
+ Copy HTML
821
+ </button>
822
+ </div>
823
+ `;
824
+
825
+ // Create content template with loading state
826
+ const litPropsTemplate =
827
+ litProps.length > 0
828
+ ? html`
829
+ <div class="lit-properties">
830
+ <h4>Lit Properties</h4>
831
+ <div class="lit-props-list">
832
+ ${litProps.map(
833
+ (prop) => html`
834
+ <div class="lit-prop">
835
+ <code class="prop-name">${prop.name}</code>
836
+ <code class="prop-value"
837
+ >${JSON.stringify(prop.value)}</code
838
+ >
839
+ </div>
840
+ `
841
+ )}
842
+ </div>
843
+ </div>
844
+ `
845
+ : nothing;
846
+
847
+ const loadingTemplate = html`
848
+ ${litPropsTemplate}
849
+ <div class="code-block-wrapper">
850
+ <pre
851
+ class="code-block"
852
+ ><code class="language-html">Loading syntax highlighting...</code></pre>
853
+ </div>
854
+ `;
855
+
856
+ // Show drawer with loading content
857
+ await drawer.show(loadingTemplate, { header: headerTemplate });
858
+
859
+ // Highlight code asynchronously
860
+ const highlightedCode = await this.highlightWithShiki(htmlCode);
861
+
862
+ // Update with highlighted code
863
+ const finalTemplate = html`
864
+ ${litPropsTemplate}
865
+ <div class="code-block-wrapper">
866
+ <pre class="code-block"><code class="language-html">${unsafeHTML(
867
+ highlightedCode
868
+ )}</code></pre>
869
+ </div>
870
+ `;
871
+
872
+ // Re-render with highlighted code
873
+ await document
874
+ .getElementById("global-drawer")
875
+ .show(finalTemplate, { header: headerTemplate });
876
+
877
+ // Add copy functionality
878
+ setTimeout(() => {
879
+ const drawer = document.getElementById("global-drawer");
880
+ const copyBtn = drawer?.querySelector("#copyCodeBtn");
881
+ if (copyBtn) {
882
+ copyBtn.onclick = () => {
883
+ let textToCopy = htmlCode;
884
+ if (litProps.length > 0) {
885
+ textToCopy =
886
+ "<!-- Lit Properties:\n" +
887
+ litProps
888
+ .map((p) => ` ${p.name}=${JSON.stringify(p.value)}`)
889
+ .join("\n") +
890
+ "\n-->\n\n" +
891
+ htmlCode;
892
+ }
893
+
894
+ navigator.clipboard.writeText(textToCopy).then(() => {
895
+ copyBtn.innerHTML = `
896
+ <pds-icon icon="check" size="sm"></pds-icon>
897
+ Copied!
898
+ `;
899
+ setTimeout(() => {
900
+ copyBtn.innerHTML = `
901
+ <pds-icon icon="clipboard" size="sm"></pds-icon>
902
+ Copy HTML
903
+ `;
904
+ }, 2000);
905
+ });
906
+ };
907
+ }
908
+ }, 100);
909
+ }
910
+
911
+ /**
912
+ * Load Shiki syntax highlighter dynamically
913
+ */
914
+ async loadShiki() {
915
+ if (this.#shiki) return this.#shiki;
916
+ if (this.#shikiLoading) {
917
+ // Wait for the loading to complete
918
+ while (this.#shikiLoading) {
919
+ await new Promise((resolve) => setTimeout(resolve, 100));
920
+ }
921
+ return this.#shiki;
922
+ }
923
+
924
+ this.#shikiLoading = true;
925
+ try {
926
+ const shiki = await import("https://esm.sh/shiki@1.0.0");
927
+ this.#shiki = await shiki.getHighlighter({
928
+ themes: ["dark-plus"],
929
+ langs: ["html"],
930
+ });
931
+ return this.#shiki;
932
+ } catch (error) {
933
+ console.error("Failed to load Shiki:", error);
934
+ return null;
935
+ } finally {
936
+ this.#shikiLoading = false;
937
+ }
938
+ }
939
+
940
+ /**
941
+ * Highlight code with Shiki
942
+ */
943
+ async highlightWithShiki(code) {
944
+ const highlighter = await this.loadShiki();
945
+ if (!highlighter) {
946
+ // Fallback to escaped HTML without highlighting
947
+ return this.escapeHTML(code);
948
+ }
949
+
950
+ try {
951
+ const html = highlighter.codeToHtml(code, {
952
+ lang: "html",
953
+ theme: "dark-plus",
954
+ });
955
+ // Extract just the code content from the generated HTML
956
+ const match = html.match(/<code[^>]*>([\s\S]*)<\/code>/);
957
+ return match ? match[1] : this.escapeHTML(code);
958
+ } catch (error) {
959
+ console.error("Shiki highlighting failed:", error);
960
+ return this.escapeHTML(code);
961
+ }
962
+ }
963
+ escapeHTML(html) {
964
+ return html
965
+ .replace(/&/g, "&amp;")
966
+ .replace(/</g, "&lt;")
967
+ .replace(/>/g, "&gt;")
968
+ .replace(/"/g, "&quot;")
969
+ .replace(/'/g, "&#039;");
970
+ }
971
+
972
+ /**
973
+ * Ensure Showdown is available (loaded from CDN) and return a Converter
974
+ */
975
+ async getShowdownConverter() {
976
+ if (this._showdown) return this._showdown;
977
+ const showdownNS = await this.loadShowdownFromCDN();
978
+ if (!showdownNS || !showdownNS.Converter) return null;
979
+ this._showdown = new showdownNS.Converter({
980
+ ghCompatibleHeaderId: true,
981
+ tables: true,
982
+ strikethrough: true,
983
+ tasklists: true,
984
+ });
985
+ return this._showdown;
986
+ }
987
+
988
+ /**
989
+ * Load Showdown from a reliable CDN (jsDelivr with unpkg fallback)
990
+ */
991
+ async loadShowdownFromCDN() {
992
+ if (typeof window !== "undefined" && window.showdown)
993
+ return window.showdown;
994
+ if (this._showdownLoading) {
995
+ // wait while another load is in progress
996
+ while (this._showdownLoading) {
997
+ await new Promise((r) => setTimeout(r, 50));
998
+ }
999
+ return window.showdown || null;
1000
+ }
1001
+
1002
+ this._showdownLoading = true;
1003
+ const urls = [
1004
+ "https://cdn.jsdelivr.net/npm/showdown@2.1.0/dist/showdown.min.js",
1005
+ "https://unpkg.com/showdown@2.1.0/dist/showdown.min.js",
1006
+ ];
1007
+
1008
+ for (const src of urls) {
1009
+ try {
1010
+ await this._injectScript(src, "showdown");
1011
+ if (window.showdown) {
1012
+ this._showdownLoading = false;
1013
+ return window.showdown;
1014
+ }
1015
+ } catch (e) {
1016
+ // try next
1017
+ }
1018
+ }
1019
+ this._showdownLoading = false;
1020
+ return null;
1021
+ }
1022
+
1023
+ _injectScript(src, libName) {
1024
+ return new Promise((resolve, reject) => {
1025
+ // Avoid duplicates
1026
+ if (
1027
+ document.querySelector(`script[data-lib="${libName}"][src="${src}"]`)
1028
+ ) {
1029
+ // Resolve on next tick
1030
+ setTimeout(resolve, 0);
1031
+ return;
1032
+ }
1033
+ const s = document.createElement("script");
1034
+ s.src = src;
1035
+ s.async = true;
1036
+ s.defer = true;
1037
+ s.dataset.lib = libName || "lib";
1038
+ s.onload = () => resolve();
1039
+ s.onerror = () => {
1040
+ s.remove();
1041
+ reject(new Error(`Failed to load script: ${src}`));
1042
+ };
1043
+ document.head.appendChild(s);
1044
+ });
1045
+ }
1046
+
1047
+ scrollToRelevantSection(fieldPath) {
1048
+ console.log("🎯 Scrolling to section for field:", fieldPath);
1049
+
1050
+ // Remove leading slash if present (pds-jsonform sends "/behavior.transitionSpeed")
1051
+ const normalizedPath = fieldPath.startsWith("/")
1052
+ ? fieldPath.slice(1)
1053
+ : fieldPath;
1054
+ console.log(" Normalized path:", normalizedPath);
1055
+
1056
+ // Map field paths to section IDs
1057
+ const sectionMap = {
1058
+ // Colors
1059
+ "colors/primary": "color-system",
1060
+ "colors/secondary": "color-system",
1061
+ "colors/accent": "color-system",
1062
+ "colors/background": "color-system",
1063
+ "colors/success": "color-system",
1064
+ "colors/warning": "color-system",
1065
+ "colors/danger": "color-system",
1066
+ "colors/info": "color-system",
1067
+
1068
+ // Typography
1069
+ "typography/": "typography",
1070
+
1071
+ // Spacing
1072
+ "spatialRhythm/": "spacing",
1073
+
1074
+ // Shadows & Layers
1075
+ "layers/": "surfaces-shadows",
1076
+
1077
+ // Shape (radius, borders)
1078
+ "shape/": "buttons",
1079
+
1080
+ // Behavior (transitions - goes to interactive states where transition demo lives)
1081
+ "behavior/transitionSpeed": "interactive-states",
1082
+ "behavior/": "interactive-states",
1083
+
1084
+ // Components
1085
+ "components/forms": "forms",
1086
+ "components/alerts": "alerts",
1087
+ "components/badges": "badges",
1088
+ "components/tables": "tables",
1089
+ "components/toasts": "toasts",
1090
+ "components/modals": "modals",
1091
+ "components/tabStrip": "tabs",
1092
+
1093
+ // Icons
1094
+ "icons/": "icons",
1095
+ };
1096
+
1097
+ // Find matching section
1098
+ let sectionId = null;
1099
+ for (const [pattern, id] of Object.entries(sectionMap)) {
1100
+ if (normalizedPath.startsWith(pattern)) {
1101
+ sectionId = id;
1102
+ console.log(` ✓ Matched pattern "${pattern}" → section "${id}"`);
1103
+ break;
1104
+ }
1105
+ }
1106
+
1107
+ if (sectionId) {
1108
+ // Find the section element
1109
+ const section = this.querySelector(`[data-section="${sectionId}"]`);
1110
+ console.log(
1111
+ ` Searching for section: [data-section="${sectionId}"]`,
1112
+ section ? "✓ Found" : "✗ Not found"
1113
+ );
1114
+
1115
+ if (section) {
1116
+ section.scrollIntoView({ behavior: "smooth", block: "start" });
1117
+
1118
+ // Add a brief highlight effect
1119
+ section.style.transition = "background-color 0.3s ease";
1120
+ section.style.backgroundColor = "var(--color-primary-50)";
1121
+ setTimeout(() => {
1122
+ section.style.backgroundColor = "";
1123
+ }, 1500);
1124
+
1125
+ console.log(" ✓ Scrolled and highlighted section");
1126
+ } else {
1127
+ console.warn(
1128
+ ` ✗ Section [data-section="${sectionId}"] not found in DOM`
1129
+ );
1130
+ }
1131
+ } else {
1132
+ console.warn(` ✗ No section mapping found for field: ${fieldPath}`);
1133
+ }
1134
+ }
1135
+
1136
+ renderDisabledSection(title, message) {
1137
+ return html`
1138
+ <section class="showcase-section disabled">
1139
+ <h2>${title}</h2>
1140
+ <p class="disabled-message">${message}</p>
1141
+ </section>
1142
+ `;
1143
+ }
1144
+
1145
+ renderTOC() {
1146
+ if (!this.sections || this.sections.length === 0) return nothing;
1147
+
1148
+ return html`
1149
+ <nav class="showcase-toc" aria-label="Table of Contents">
1150
+ <div class="toc-wrapper">
1151
+ <div class="input-icon">
1152
+ <pds-icon icon="magnifying-glass"></pds-icon>
1153
+ <input
1154
+ id="pds-search"
1155
+ @focus=${(e) =>
1156
+ AutoComplete.connect(e, this.autoCompleteSettings)}
1157
+ type="search"
1158
+ placeholder="Search design system..."
1159
+ />
1160
+ </div>
1161
+ </div>
1162
+ </nav>
1163
+ `;
1164
+ }
1165
+
1166
+ get autoCompleteSettings() {
1167
+ return {
1168
+ //debug: true,
1169
+ iconHandler: (item) => {
1170
+ return item.icon ? `<pds-icon icon="${item.icon}"></pds-icon>` : null;
1171
+ },
1172
+ categories: {
1173
+ Sections: {
1174
+ action: (options) => {
1175
+ document
1176
+ .querySelector(`[data-section="${options.id}"]`)
1177
+ .scrollIntoView({ behavior: "smooth", block: "start" });
1178
+ },
1179
+ trigger: (options) => options.search.length === 0,
1180
+ getItems: (options) => {
1181
+ return this.sections.map((section) => {
1182
+ return {
1183
+ text: section.title,
1184
+ id: section.id,
1185
+ icon: "folder-simple",
1186
+ };
1187
+ });
1188
+ },
1189
+ },
1190
+ Query: {
1191
+ action: (options) => {
1192
+ // For query results, display the code/value or scroll to relevant section
1193
+ if (options.code) {
1194
+ // If there's code, copy to clipboard
1195
+ if (navigator.clipboard) {
1196
+ navigator.clipboard.writeText(options.code).then(() => {
1197
+ PDS.dispatchEvent(
1198
+ new CustomEvent("pds:toast", {
1199
+ detail: {
1200
+ message: "Code copied to clipboard",
1201
+ type: "success",
1202
+ duration: 2000,
1203
+ },
1204
+ })
1205
+ );
1206
+ });
1207
+ }
1208
+ }
1209
+ // Also try to navigate to relevant section if available
1210
+ const category = options.category?.toLowerCase() || "";
1211
+ let sectionId = null;
1212
+
1213
+ if (category.includes("color") || category.includes("surface")) {
1214
+ sectionId = "color-system";
1215
+ } else if (
1216
+ category.includes("utility") ||
1217
+ category.includes("layout")
1218
+ ) {
1219
+ sectionId = "utilities";
1220
+ } else if (category.includes("component")) {
1221
+ sectionId = "components";
1222
+ } else if (category.includes("typography")) {
1223
+ sectionId = "typography";
1224
+ } else if (category.includes("spacing")) {
1225
+ sectionId = "spacing";
1226
+ }
1227
+
1228
+ if (sectionId) {
1229
+ const el = document.querySelector(
1230
+ `[data-section="${sectionId}"]`
1231
+ );
1232
+ if (el) {
1233
+ el.scrollIntoView({ behavior: "smooth", block: "start" });
1234
+ }
1235
+ }
1236
+ },
1237
+ trigger: (options) => options.search.length >= 2,
1238
+ getItems: async (options) => {
1239
+ const q = (options.search || "").trim();
1240
+ if (!q) return [];
1241
+
1242
+ try {
1243
+ const results = await PDS.query(q);
1244
+
1245
+ return results.map((result) => {
1246
+ return {
1247
+ text: result.text,
1248
+ id: result.value,
1249
+ icon: result.icon || "magnifying-glass",
1250
+ category: result.category,
1251
+ code: result.code,
1252
+ cssVar: result.cssVar,
1253
+ description: result.description,
1254
+ };
1255
+ });
1256
+ } catch (err) {
1257
+ console.error("Query error:", err);
1258
+ return [];
1259
+ }
1260
+ },
1261
+ },
1262
+ Search: {
1263
+ action: (options) => {
1264
+ // When a user selects an item, try to resolve it to a showcase section
1265
+ const rawId = options.id || "";
1266
+ const query = (options.search || "").toLowerCase();
1267
+
1268
+ // id is encoded as `type|key` (see getItems)
1269
+ const [type, key] = rawId.split("|");
1270
+
1271
+ // 1) try to find a section with id exactly matching key
1272
+ let section = this.sections.find((s) => s.id === key);
1273
+
1274
+ // 2) try to match against title or id containing the key or query
1275
+ if (!section) {
1276
+ section = this.sections.find(
1277
+ (s) =>
1278
+ s.title
1279
+ ?.toLowerCase()
1280
+ .includes(key?.toLowerCase?.() || "") ||
1281
+ s.id?.toLowerCase().includes(key?.toLowerCase?.() || "") ||
1282
+ s.title?.toLowerCase().includes(query) ||
1283
+ s.id?.toLowerCase().includes(query)
1284
+ );
1285
+ }
1286
+
1287
+ // 3) fallback: search inside each section element for the query text
1288
+ if (!section && query) {
1289
+ for (const s of this.sections) {
1290
+ const el = this.querySelector(`[data-section="${s.id}"]`);
1291
+ if (!el) continue;
1292
+ const text = (el.innerText || "").toLowerCase();
1293
+ if (
1294
+ text.includes(query) ||
1295
+ s.title.toLowerCase().includes(query)
1296
+ ) {
1297
+ section = s;
1298
+ break;
1299
+ }
1300
+ }
1301
+ }
1302
+
1303
+ if (section) {
1304
+ const el = this.querySelector(`[data-section="${section.id}"]`);
1305
+ if (el)
1306
+ el.scrollIntoView({ behavior: "smooth", block: "start" });
1307
+ } else {
1308
+ // no section found - try a global text search and show first match
1309
+ const allText = (this.innerText || "").toLowerCase();
1310
+ const idx = allText.indexOf(query);
1311
+ if (idx !== -1) {
1312
+ // find first section that contains the query
1313
+ for (const s of this.sections) {
1314
+ const el = this.querySelector(`[data-section="${s.id}"]`);
1315
+ if (!el) continue;
1316
+ if ((el.innerText || "").toLowerCase().includes(query)) {
1317
+ el.scrollIntoView({ behavior: "smooth", block: "start" });
1318
+ break;
1319
+ }
1320
+ }
1321
+ }
1322
+ }
1323
+ },
1324
+ trigger: (options) => options.search.length > 1,
1325
+ getItems: (options) => {
1326
+ const q = (options.search || "").trim().toLowerCase();
1327
+ if (!q) return [];
1328
+
1329
+ const candidates = [];
1330
+
1331
+ // primitives
1332
+ for (const p of PDS.ontology.primitives || []) {
1333
+ const name = (p.name || p.id || "").toString();
1334
+ const id = p.id || name.replace(/\s+/g, "-").toLowerCase();
1335
+ const score =
1336
+ (name.toLowerCase().includes(q) ? 30 : 0) +
1337
+ (id.includes(q) ? 20 : 0);
1338
+ // selectors
1339
+ const selMatch = (p.selectors || []).some((s) =>
1340
+ String(s).toLowerCase().includes(q)
1341
+ );
1342
+ const total = score + (selMatch ? 10 : 0);
1343
+ candidates.push({
1344
+ type: "primitive",
1345
+ key: id,
1346
+ name,
1347
+ score: total,
1348
+ });
1349
+ }
1350
+
1351
+ // components
1352
+ for (const c of PDS.ontology.components || []) {
1353
+ const name = (c.name || c.id || "").toString();
1354
+ const id = c.id || name.replace(/\s+/g, "-").toLowerCase();
1355
+ const score =
1356
+ (name.toLowerCase().includes(q) ? 40 : 0) +
1357
+ (id.includes(q) ? 25 : 0);
1358
+ const selMatch = (c.selectors || []).some((s) =>
1359
+ String(s).toLowerCase().includes(q)
1360
+ );
1361
+ const total = score + (selMatch ? 10 : 0);
1362
+ candidates.push({
1363
+ type: "component",
1364
+ key: id,
1365
+ name,
1366
+ score: total,
1367
+ });
1368
+ }
1369
+
1370
+ // tokens (flatten groups)
1371
+ if (PDS.ontology.tokens) {
1372
+ for (const [group, items] of Object.entries(
1373
+ PDS.ontology.tokens
1374
+ )) {
1375
+ if (Array.isArray(items)) {
1376
+ for (const t of items) {
1377
+ const name = `${group}/${t}`;
1378
+ const id = `${group}-${t}`;
1379
+ const score = name.toLowerCase().includes(q) ? 35 : 0;
1380
+ candidates.push({ type: "token", key: id, name, score });
1381
+ }
1382
+ }
1383
+ }
1384
+ }
1385
+
1386
+ // enhancements
1387
+ for (const enh of PDS.ontology.enhancements || []) {
1388
+ const name = String(enh);
1389
+ const key = `enh-${name.replace(
1390
+ /[^a-z0-9]+/gi,
1391
+ "-"
1392
+ )}`.toLowerCase();
1393
+ const score = name.toLowerCase().includes(q) ? 25 : 0;
1394
+ candidates.push({ type: "enhancement", key, name, score });
1395
+ }
1396
+
1397
+ // utilities
1398
+ for (const util of PDS.ontology.utilities || []) {
1399
+ const name = String(util);
1400
+ const key = `util-${name.replace(
1401
+ /[^a-z0-9]+/gi,
1402
+ "-"
1403
+ )}`.toLowerCase();
1404
+ const score = name.toLowerCase().includes(q) ? 20 : 0;
1405
+ candidates.push({ type: "utility", key, name, score });
1406
+ }
1407
+
1408
+ // styles (flat)
1409
+ if (PDS.ontology.styles) {
1410
+ for (const [k, v] of Object.entries(PDS.ontology.styles)) {
1411
+ const name = k;
1412
+ const key = `style-${k}`;
1413
+ const score = name.toLowerCase().includes(q) ? 10 : 0;
1414
+ candidates.push({ type: "style", key, name, score });
1415
+ }
1416
+ }
1417
+
1418
+ // Basic fuzzy/substring scoring boost for any candidate containing q in name/key
1419
+ for (const c of candidates) {
1420
+ const lname = (c.name || "").toLowerCase();
1421
+ const lkey = (c.key || "").toLowerCase();
1422
+ if (lname.includes(q)) c.score += 50;
1423
+ if (lkey.includes(q)) c.score += 20;
1424
+ }
1425
+
1426
+ // Filter and sort
1427
+ const results = candidates
1428
+ .filter((c) => c.score > 0)
1429
+ .sort((a, b) => b.score - a.score)
1430
+ .slice(0, 30)
1431
+ .map((c) => ({
1432
+ text: c.name,
1433
+ id: `${c.type}|${c.key}`,
1434
+ icon:
1435
+ c.type === "component"
1436
+ ? "brackets-curly"
1437
+ : c.type === "primitive"
1438
+ ? "tag"
1439
+ : c.type === "token"
1440
+ ? "palette"
1441
+ : "folder-simple",
1442
+ }));
1443
+
1444
+ return results;
1445
+ },
1446
+ },
1447
+ },
1448
+ };
1449
+ }
1450
+
1451
+ render() {
1452
+ const components = this.config?.components || {};
1453
+ // Determine current theme from DOM so section copy can adapt
1454
+ const theme =
1455
+ (typeof document !== "undefined" &&
1456
+ document.documentElement?.getAttribute("data-theme")) ||
1457
+ "light";
1458
+ const inversionTitle =
1459
+ theme === "dark"
1460
+ ? "Light Surfaces in Dark Mode"
1461
+ : "Dark Surfaces in Light Mode";
1462
+ const inversePhrase =
1463
+ theme === "dark"
1464
+ ? "light surface in dark mode"
1465
+ : "dark surface in light mode";
1466
+
1467
+ return html`
1468
+ <div
1469
+ class="showcase-container ${this.inspectorActive
1470
+ ? "inspector-active"
1471
+ : ""}"
1472
+ @click=${this.handleInspectorClick}
1473
+ >
1474
+ <!-- Table of Contents Navigation -->
1475
+ ${this.renderTOC()}
1476
+
1477
+ <!-- Hero Section -->
1478
+ <section class="showcase-hero">
1479
+ <h1>Pure Design System</h1>
1480
+ <p>Why build a design system if you can generate it?</p>
1481
+ <div class="btn-group">
1482
+ <button
1483
+ class="btn-primary btn-lg"
1484
+ @click=${() => {
1485
+ this.showDoc("getting-started.md");
1486
+ }}
1487
+ >
1488
+ <pds-icon icon="download"></pds-icon>
1489
+ Get Started
1490
+ </button>
1491
+ <button
1492
+ class="btn-secondary btn-lg"
1493
+ @click=${() => {
1494
+ this.showDoc("readme.md");
1495
+ }}
1496
+ >
1497
+ <pds-icon icon="book-open"></pds-icon>
1498
+ View Docs
1499
+ </button>
1500
+ </div>
1501
+ </section>
1502
+
1503
+ <!-- Colors Section -->
1504
+ <section class="showcase-section" data-section="color-system">
1505
+ <h2>
1506
+ <pds-icon
1507
+ icon="palette"
1508
+ size="lg"
1509
+ class="icon-primary"
1510
+ ></pds-icon>
1511
+ Color System
1512
+ </h2>
1513
+
1514
+
1515
+ <div class="color-grid">
1516
+ ${this.renderColorCard("Primary", "primary")}
1517
+ ${this.renderColorCard("Secondary", "secondary")}
1518
+ ${this.renderColorCard("Accent", "accent")}
1519
+ ${this.renderColorCard("Success", "success")}
1520
+ ${this.renderColorCard("Warning", "warning")}
1521
+ ${this.renderColorCard("Danger", "danger")}
1522
+ ${this.renderColorCard("Info", "info")}
1523
+ </div>
1524
+
1525
+ <h3>Semantic Color Usage</h3>
1526
+ <div class="semantic-usage">
1527
+ <div class="semantic-message success">
1528
+ <pds-icon
1529
+ icon="check-circle"
1530
+ class="icon-success"
1531
+ size="lg"
1532
+ ></pds-icon>
1533
+ <div>
1534
+ <strong>Success</strong>
1535
+ <p>Operations completed successfully</p>
1536
+ </div>
1537
+ </div>
1538
+
1539
+ <div class="semantic-message warning">
1540
+ <pds-icon
1541
+ icon="warning"
1542
+ class="icon-warning"
1543
+ size="lg"
1544
+ ></pds-icon>
1545
+ <div>
1546
+ <strong>Warning</strong>
1547
+ <p>Please review carefully</p>
1548
+ </div>
1549
+ </div>
1550
+
1551
+ <div class="semantic-message danger">
1552
+ <pds-icon
1553
+ icon="x-circle"
1554
+ class="icon-danger"
1555
+ size="lg"
1556
+ ></pds-icon>
1557
+ <div>
1558
+ <strong>Danger</strong>
1559
+ <p>Critical error occurred</p>
1560
+ </div>
1561
+ </div>
1562
+
1563
+ <div class="semantic-message info">
1564
+ <pds-icon icon="info" class="icon-info" size="lg"></pds-icon>
1565
+ <div>
1566
+ <strong>Info</strong>
1567
+ <p>Helpful information</p>
1568
+ </div>
1569
+ </div>
1570
+ </div>
1571
+
1572
+ <h3>Gray Scale (from Secondary)</h3>
1573
+ <div class="gray-scale-grid">
1574
+ ${[50, 100, 200, 300, 400, 500, 600, 700, 800].map(
1575
+ (shade) => html`
1576
+ <div class="gray-scale-item">
1577
+ <div
1578
+ class="gray-scale-swatch"
1579
+ style="background-color: var(--color-gray-${shade});"
1580
+ title="gray-${shade}"
1581
+ ></div>
1582
+ <div class="gray-scale-label">${shade}</div>
1583
+ </div>
1584
+ `
1585
+ )}
1586
+ </div>
1587
+ </section>
1588
+
1589
+ <!-- Derived Color Scales Section -->
1590
+ <section class="showcase-section alt-bg">
1591
+ <h2>
1592
+ <pds-icon icon="sun" size="lg" class="icon-warning"></pds-icon>
1593
+ Derived Color Scales
1594
+ </h2>
1595
+ <p>
1596
+ Complete 9-step color scales (50-800) automatically generated from
1597
+ your base colors. Each scale maintains proper contrast and color
1598
+ relationships.
1599
+ </p>
1600
+
1601
+ <h3>Primary Color Scale</h3>
1602
+ ${this.renderColorScale("primary")}
1603
+
1604
+ <h3>Secondary (Neutral) Scale</h3>
1605
+ ${this.renderColorScale("secondary")}
1606
+
1607
+ <h3>Accent Color Scale</h3>
1608
+ ${this.renderColorScale("accent")}
1609
+
1610
+ <h3>Semantic Color Scales (Auto-Derived)</h3>
1611
+ <p class="interactive-demo">
1612
+ These colors are automatically derived from your primary color
1613
+ with intelligent hue shifting for semantic meaning.
1614
+ </p>
1615
+
1616
+ ${this.renderColorScale("success")}
1617
+ ${this.renderColorScale("warning")}
1618
+ ${this.renderColorScale("danger")} ${this.renderColorScale("info")}
1619
+ </section>
1620
+
1621
+ <!-- Typography Section -->
1622
+ <section class="showcase-section alt-bg" data-section="typography">
1623
+ <h2>
1624
+ <pds-icon
1625
+ icon="text-aa"
1626
+ size="lg"
1627
+ class="icon-primary"
1628
+ ></pds-icon>
1629
+ Typography
1630
+ </h2>
1631
+
1632
+ <div class="grid grid-cols-1">
1633
+ <h1>Heading 1 - The quick brown fox</h1>
1634
+ <h2>Heading 2 - The quick brown fox</h2>
1635
+ <h3>Heading 3 - The quick brown fox</h3>
1636
+ <h4>Heading 4 - The quick brown fox</h4>
1637
+ <h5>Heading 5 - The quick brown fox</h5>
1638
+ <h6>Heading 6 - The quick brown fox</h6>
1639
+ <p>
1640
+ Regular paragraph text with <a href="#">a link</a> and
1641
+ <code>inline code</code>.
1642
+ </p>
1643
+ </div>
1644
+ </section>
1645
+
1646
+ <!-- Buttons Section -->
1647
+ <section class="showcase-section" data-section="buttons">
1648
+ <h2>
1649
+ <pds-icon
1650
+ icon="cursor-click"
1651
+ size="lg"
1652
+ class="icon-primary"
1653
+ ></pds-icon>
1654
+ Buttons
1655
+ </h2>
1656
+
1657
+ <div class="flex-wrap">
1658
+ <button class="btn-primary">Primary</button>
1659
+ <button class="btn-secondary">Secondary</button>
1660
+ <button class="btn-outline">Outline</button>
1661
+ <button class="btn-primary" disabled>Disabled</button>
1662
+ </div>
1663
+
1664
+ <h3>Sizes</h3>
1665
+ <div class="flex-wrap">
1666
+ <button class="btn-primary btn-sm">Small</button>
1667
+ <button class="btn-primary">Default</button>
1668
+ <button class="btn-primary btn-lg">Large</button>
1669
+ </div>
1670
+
1671
+ <h3>Icon Buttons</h3>
1672
+ <div class="flex-wrap gap-sm">
1673
+ <button class="icon-only btn-primary">
1674
+ <pds-icon icon="gear" label="Settings"></pds-icon>
1675
+ </button>
1676
+ <button class="icon-only btn-secondary">
1677
+ <pds-icon icon="bell" label="Notifications"></pds-icon>
1678
+ </button>
1679
+ <button class="icon-only btn-outline">
1680
+ <pds-icon icon="heart" label="Favorite"></pds-icon>
1681
+ </button>
1682
+ <button class="btn-primary">
1683
+ <pds-icon icon="download"></pds-icon>
1684
+ <span>Download</span>
1685
+ </button>
1686
+ </div>
1687
+ </section>
1688
+
1689
+ <section class="showcase-section alt-bg" data-section="forms">
1690
+ <h2>
1691
+ <pds-icon
1692
+ icon="note-pencil"
1693
+ size="lg"
1694
+ class="icon-primary"
1695
+ ></pds-icon>
1696
+ Form Controls
1697
+ </h2>
1698
+
1699
+ <form
1700
+ class="form-demo"
1701
+ onsubmit="event.preventDefault(); console.log('Form submitted (prevented)'); return false;"
1702
+ >
1703
+ <fieldset>
1704
+ <legend>Personal Information</legend>
1705
+
1706
+ <label>
1707
+ <span>Full Name</span>
1708
+ <input type="text" placeholder="Enter your name" required />
1709
+ </label>
1710
+
1711
+ <label>
1712
+ <span>Email</span>
1713
+ <input
1714
+ type="email"
1715
+ placeholder="you@example.com"
1716
+ autocomplete="username"
1717
+ required
1718
+ />
1719
+ </label>
1720
+
1721
+ <label>
1722
+ <span>Phone</span>
1723
+ <input type="tel" placeholder="+1 (555) 000-0000" />
1724
+ </label>
1725
+
1726
+ <label>
1727
+ <span>Date of Birth</span>
1728
+ <input type="date" />
1729
+ </label>
1730
+
1731
+ <label>
1732
+ <span>Rich Text</span>
1733
+ <pds-richtext
1734
+ name="richtext"
1735
+ placeholder="Enter rich text"
1736
+ ></pds-richtext>
1737
+ </label>
1738
+
1739
+ <label>
1740
+ <span>Password</span>
1741
+ <input
1742
+ type="password"
1743
+ placeholder="Enter password"
1744
+ autocomplete="current-password"
1745
+ />
1746
+ </label>
1747
+
1748
+ <label>
1749
+ <span>Country</span>
1750
+ <select>
1751
+ <option>United States</option>
1752
+ <option>Canada</option>
1753
+ <option>United Kingdom</option>
1754
+ <option>Germany</option>
1755
+ <option>France</option>
1756
+ <option>Other</option>
1757
+ </select>
1758
+ </label>
1759
+
1760
+ <label>
1761
+ <span>Number</span>
1762
+ <input type="number" min="0" max="100" value="50" />
1763
+ </label>
1764
+
1765
+ <label>
1766
+ <span>Range</span>
1767
+ <input type="range" min="0" max="100" value="50" />
1768
+ </label>
1769
+
1770
+ <label>
1771
+ <span>URL</span>
1772
+ <input type="url" placeholder="https://example.com" />
1773
+ </label>
1774
+
1775
+ <label>
1776
+ <span>File Upload</span>
1777
+ <pds-upload name="file"></pds-upload>
1778
+ </label>
1779
+
1780
+ <label>
1781
+ <span>Message</span>
1782
+ <textarea
1783
+ rows="4"
1784
+ placeholder="Your message here..."
1785
+ ></textarea>
1786
+ </label>
1787
+ </fieldset>
1788
+
1789
+ <fieldset>
1790
+ <legend>Preferences</legend>
1791
+
1792
+ <label data-toggle>
1793
+ <input type="checkbox" checked />
1794
+ Subscribe to newsletter
1795
+ </label>
1796
+
1797
+ <label data-toggle>
1798
+ <input type="checkbox" />
1799
+ Receive marketing emails
1800
+ </label>
1801
+ </fieldset>
1802
+
1803
+ <fieldset>
1804
+ <legend>Toggle Switches (Progressive Enhancement)</legend>
1805
+
1806
+ <label data-toggle>
1807
+ <input type="checkbox" checked />
1808
+ Enable notifications
1809
+ </label>
1810
+
1811
+ <label data-toggle>
1812
+ <input type="checkbox" />
1813
+ Dark mode
1814
+ </label>
1815
+
1816
+ <label data-toggle>
1817
+ <input type="checkbox" checked />
1818
+ Auto-save
1819
+ </label>
1820
+ </fieldset>
1821
+
1822
+ <div class="form-demo-actions">
1823
+ <button type="submit" class="btn-primary">Submit Form</button>
1824
+ <button type="reset" class="btn-secondary">Reset</button>
1825
+ <button type="button" class="btn-outline">Cancel</button>
1826
+ </div>
1827
+ </form>
1828
+ </section>
1829
+
1830
+ <section class="showcase-section" data-section="alerts">
1831
+ <h2>
1832
+ <pds-icon
1833
+ icon="bell-ringing"
1834
+ size="lg"
1835
+ class="icon-primary"
1836
+ ></pds-icon>
1837
+ Alerts & Feedback
1838
+ </h2>
1839
+
1840
+ <div class="grid grid-cols-1">
1841
+ <div class="alert alert-success">
1842
+ <div class="alert-icon">
1843
+ <pds-icon
1844
+ icon="check-circle"
1845
+ class="icon-success"
1846
+ size="lg"
1847
+ ></pds-icon>
1848
+ </div>
1849
+ <div>
1850
+ <div class="alert-title">Success!</div>
1851
+ <p>Your operation completed successfully.</p>
1852
+ </div>
1853
+ </div>
1854
+
1855
+ <div class="alert alert-info">
1856
+ <div class="alert-icon">
1857
+ <pds-icon icon="info" class="icon-info" size="lg"></pds-icon>
1858
+ </div>
1859
+ <div>
1860
+ <div class="alert-title">Information</div>
1861
+ <p>This is an informational message.</p>
1862
+ </div>
1863
+ </div>
1864
+
1865
+ <div class="alert alert-warning">
1866
+ <div class="alert-icon">
1867
+ <pds-icon
1868
+ icon="warning"
1869
+ class="icon-warning"
1870
+ size="lg"
1871
+ ></pds-icon>
1872
+ </div>
1873
+ <div>
1874
+ <div class="alert-title">Warning</div>
1875
+ <p>Please review this warning.</p>
1876
+ </div>
1877
+ </div>
1878
+
1879
+ <div class="alert alert-danger">
1880
+ <div class="alert-icon">
1881
+ <pds-icon
1882
+ icon="x-circle"
1883
+ class="icon-danger"
1884
+ size="lg"
1885
+ ></pds-icon>
1886
+ </div>
1887
+ <div>
1888
+ <div class="alert-title">Error</div>
1889
+ <p>An error occurred.</p>
1890
+ </div>
1891
+ </div>
1892
+ </div>
1893
+ </section>
1894
+
1895
+ <!-- Badges Section -->
1896
+
1897
+ <section class="showcase-section alt-bg">
1898
+ <h2>
1899
+ <pds-icon icon="tag" size="lg" class="icon-primary"></pds-icon>
1900
+ Badges & Pills
1901
+ </h2>
1902
+
1903
+ <h3>Default Badges</h3>
1904
+ <div class="badge-grid">
1905
+ <span class="badge">Default</span>
1906
+ <span class="badge badge-primary">Primary</span>
1907
+ <span class="badge badge-secondary">Secondary</span>
1908
+ <span class="badge badge-success">Success</span>
1909
+ <span class="badge badge-warning">Warning</span>
1910
+ <span class="badge badge-danger">Danger</span>
1911
+ <span class="badge badge-info">Info</span>
1912
+ </div>
1913
+
1914
+ <h3>Outlined Badges</h3>
1915
+ <div class="badge-grid">
1916
+ <span class="badge badge-outline badge-primary">Primary</span>
1917
+ <span class="badge badge-outline badge-secondary">Secondary</span>
1918
+ <span class="badge badge-outline badge-success">Success</span>
1919
+ <span class="badge badge-outline badge-info">Info</span>
1920
+ <span class="badge badge-outline badge-warning">Warning</span>
1921
+ <span class="badge badge-outline badge-danger">Danger</span>
1922
+ </div>
1923
+
1924
+ <h3>Badge Sizes</h3>
1925
+ <div class="size-demo">
1926
+ <span class="badge badge-primary badge-sm">Small</span>
1927
+ <span class="badge badge-primary">Default</span>
1928
+ <span class="badge badge-primary badge-lg">Large</span>
1929
+ </div>
1930
+
1931
+ <h3>Pills</h3>
1932
+ <div class="badge-grid">
1933
+ <span class="pill badge-primary">React</span>
1934
+ <span class="pill badge-secondary">Vue</span>
1935
+ <span class="pill badge-success">Node.js</span>
1936
+ <span class="pill badge-info">TypeScript</span>
1937
+ <span class="pill badge-warning">JavaScript</span>
1938
+ <span class="pill badge-danger">Critical</span>
1939
+ </div>
1940
+ </section>
1941
+
1942
+ <!-- Media Elements Section -->
1943
+ <section class="showcase-section">
1944
+ <h2>
1945
+ <pds-icon icon="image" size="lg" class="icon-primary"></pds-icon>
1946
+ Media Elements
1947
+ </h2>
1948
+
1949
+ <h3>Responsive Images</h3>
1950
+ <div class="media-grid">
1951
+ <figure class="media-figure">
1952
+ <img
1953
+ class="media-image"
1954
+ src="https://picsum.photos/800/600?random=1"
1955
+ alt="Random landscape"
1956
+ />
1957
+ <figcaption class="media-caption">
1958
+ <strong>Figure 1:</strong> A beautiful landscape demonstrating
1959
+ image handling in the design system.
1960
+ </figcaption>
1961
+ </figure>
1962
+
1963
+ <figure class="media-figure">
1964
+ <img
1965
+ class="media-image"
1966
+ src="https://picsum.photos/800/600?random=2"
1967
+ alt="Random architecture"
1968
+ />
1969
+ <figcaption class="media-caption">
1970
+ <strong>Figure 2:</strong> Architectural photography
1971
+ showcasing the responsive image behavior.
1972
+ </figcaption>
1973
+ </figure>
1974
+ </div>
1975
+
1976
+ <h3>Image Gallery</h3>
1977
+ <div class="gallery-grid">
1978
+ <img
1979
+ class="gallery-image"
1980
+ src="https://picsum.photos/400/400?random=3"
1981
+ alt="Gallery image 1"
1982
+ />
1983
+ <img
1984
+ class="gallery-image"
1985
+ src="https://picsum.photos/400/400?random=4"
1986
+ alt="Gallery image 2"
1987
+ />
1988
+ <img
1989
+ class="gallery-image"
1990
+ src="https://picsum.photos/400/400?random=5"
1991
+ alt="Gallery image 3"
1992
+ />
1993
+ <img
1994
+ class="gallery-image"
1995
+ src="https://picsum.photos/400/400?random=6"
1996
+ alt="Gallery image 4"
1997
+ />
1998
+ </div>
1999
+
2000
+ <h3>Netflix Row</h3>
2001
+
2002
+ <pds-scrollrow>
2003
+ ${new Array(20)
2004
+ .fill(0)
2005
+ .map(
2006
+ (_, i) => html`<img
2007
+ loading="lazy"
2008
+ class="scroll-row-image"
2009
+ src="https://picsum.photos/200/200?random=${i + 1}"
2010
+ alt="Gallery image ${i + 1}"
2011
+ />`
2012
+ )}
2013
+ </pds-scrollrow>
2014
+
2015
+ <h3>Video Element</h3>
2016
+ <figure class="video-container">
2017
+ <video
2018
+ class="video-element"
2019
+ controls
2020
+ poster="https://picsum.photos/1200/675?random=7"
2021
+ >
2022
+ <source
2023
+ src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
2024
+ type="video/mp4"
2025
+ />
2026
+ Your browser does not support the video tag.
2027
+ </video>
2028
+ <figcaption class="media-caption">
2029
+ <strong>Video Demo:</strong> Big Buck Bunny sample video
2030
+ demonstrating video element styling.
2031
+ </figcaption>
2032
+ </figure>
2033
+ </section>
2034
+
2035
+ <!-- Enhanced Components Section -->
2036
+ <section class="showcase-section alt-bg">
2037
+ <h2>
2038
+ <pds-icon
2039
+ icon="brackets-curly"
2040
+ size="lg"
2041
+ class="icon-primary"
2042
+ ></pds-icon>
2043
+ Enhanced Components
2044
+ </h2>
2045
+
2046
+ <h3>Dropdown Menu (Progressive Enhancement)</h3>
2047
+ <p class="dropdown-demo">
2048
+ Click the button to toggle the dropdown menu:
2049
+ </p>
2050
+ <nav data-dropdown>
2051
+ <button class="btn-primary">
2052
+ <pds-icon icon="list"></pds-icon>
2053
+ Click for Menu
2054
+ </button>
2055
+ <menu class="dropdown-menu liquid-glass">
2056
+ <li>
2057
+ <a href="#">
2058
+ <pds-icon icon="user" size="sm"></pds-icon>
2059
+ Profile
2060
+ </a>
2061
+ </li>
2062
+ <li>
2063
+ <a href="#">
2064
+ <pds-icon icon="gear" size="sm"></pds-icon>
2065
+ Settings
2066
+ </a>
2067
+ </li>
2068
+ <li>
2069
+ <a href="#" class="danger">
2070
+ <pds-icon icon="x" size="sm"></pds-icon>
2071
+ Logout
2072
+ </a>
2073
+ </li>
2074
+ </menu>
2075
+ </nav>
2076
+ </section>
2077
+
2078
+ <!-- Icons Section -->
2079
+ <section class="showcase-section" data-section="icons">
2080
+ <h2>
2081
+ <pds-icon icon="star" size="lg" class="icon-accent"></pds-icon>
2082
+ Icon System
2083
+ </h2>
2084
+
2085
+ <h3>Sizes</h3>
2086
+ <div class="icon-sizes surface-translucent">
2087
+ <pds-icon icon="heart" size="xs"></pds-icon>
2088
+ <pds-icon icon="heart" size="sm"></pds-icon>
2089
+ <pds-icon icon="heart" size="md"></pds-icon>
2090
+ <pds-icon icon="heart" size="lg"></pds-icon>
2091
+ <pds-icon icon="heart" size="xl"></pds-icon>
2092
+ <pds-icon icon="heart" size="2xl"></pds-icon>
2093
+ </div>
2094
+
2095
+ <h3>Semantic Colors</h3>
2096
+ <div class="icon-colors surface-translucent">
2097
+ <pds-icon
2098
+ icon="check-circle"
2099
+ class="icon-primary"
2100
+ size="lg"
2101
+ ></pds-icon>
2102
+ <pds-icon
2103
+ icon="check-circle"
2104
+ class="icon-secondary"
2105
+ size="lg"
2106
+ ></pds-icon>
2107
+ <pds-icon
2108
+ icon="check-circle"
2109
+ class="icon-accent"
2110
+ size="lg"
2111
+ ></pds-icon>
2112
+ <pds-icon
2113
+ icon="check-circle"
2114
+ class="icon-success"
2115
+ size="lg"
2116
+ ></pds-icon>
2117
+ <pds-icon
2118
+ icon="warning"
2119
+ class="icon-warning"
2120
+ size="lg"
2121
+ ></pds-icon>
2122
+ <pds-icon
2123
+ icon="x-circle"
2124
+ class="icon-danger"
2125
+ size="lg"
2126
+ ></pds-icon>
2127
+ <pds-icon icon="info" class="icon-info" size="lg"></pds-icon>
2128
+ </div>
2129
+
2130
+ <h3>Icon with Text</h3>
2131
+ <div class="icon-text-demo">
2132
+ <div class="icon-text">
2133
+ <pds-icon icon="envelope"></pds-icon>
2134
+ <span>Email</span>
2135
+ </div>
2136
+ <div class="icon-text">
2137
+ <pds-icon icon="phone"></pds-icon>
2138
+ <span>Phone</span>
2139
+ </div>
2140
+ <div class="icon-text">
2141
+ <pds-icon icon="user"></pds-icon>
2142
+ <span>Profile</span>
2143
+ </div>
2144
+ <div class="icon-text">
2145
+ <pds-icon icon="calendar"></pds-icon>
2146
+ <span>Schedule</span>
2147
+ </div>
2148
+ </div>
2149
+
2150
+ <h3>Inputs with Icons</h3>
2151
+ <div class="input-icon-demo">
2152
+ <div class="input-icon">
2153
+ <pds-icon icon="magnifying-glass"></pds-icon>
2154
+ <input type="search" placeholder="Search..." />
2155
+ </div>
2156
+ <div class="input-icon input-icon-end">
2157
+ <input type="text" placeholder="Username" />
2158
+ <pds-icon icon="user"></pds-icon>
2159
+ </div>
2160
+ </div>
2161
+
2162
+ <h3>Common Icons</h3>
2163
+ <div class="icon-grid surface-translucent">
2164
+ <div class="icon-grid-item">
2165
+ <pds-icon icon="house" size="lg"></pds-icon>
2166
+ <span class="icon-grid-label">house</span>
2167
+ </div>
2168
+ <div class="icon-grid-item">
2169
+ <pds-icon icon="gear" size="lg"></pds-icon>
2170
+ <span class="icon-grid-label">gear</span>
2171
+ </div>
2172
+ <div class="icon-grid-item">
2173
+ <pds-icon icon="bell" size="lg"></pds-icon>
2174
+ <span class="icon-grid-label">bell</span>
2175
+ </div>
2176
+ <div class="icon-grid-item">
2177
+ <pds-icon icon="heart" size="lg"></pds-icon>
2178
+ <span class="icon-grid-label">heart</span>
2179
+ </div>
2180
+ <div class="icon-grid-item">
2181
+ <pds-icon icon="star" size="lg"></pds-icon>
2182
+ <span class="icon-grid-label">star</span>
2183
+ </div>
2184
+ <div class="icon-grid-item">
2185
+ <pds-icon icon="trash" size="lg"></pds-icon>
2186
+ <span class="icon-grid-label">trash</span>
2187
+ </div>
2188
+ <div class="icon-grid-item">
2189
+ <pds-icon icon="pencil" size="lg"></pds-icon>
2190
+ <span class="icon-grid-label">pencil</span>
2191
+ </div>
2192
+ <div class="icon-grid-item">
2193
+ <pds-icon icon="check" size="lg"></pds-icon>
2194
+ <span class="icon-grid-label">check</span>
2195
+ </div>
2196
+ <div class="icon-grid-item">
2197
+ <pds-icon icon="x" size="lg"></pds-icon>
2198
+ <span class="icon-grid-label">x</span>
2199
+ </div>
2200
+ <div class="icon-grid-item">
2201
+ <pds-icon icon="plus" size="lg"></pds-icon>
2202
+ <span class="icon-grid-label">plus</span>
2203
+ </div>
2204
+ <div class="icon-grid-item">
2205
+ <pds-icon icon="minus" size="lg"></pds-icon>
2206
+ <span class="icon-grid-label">minus</span>
2207
+ </div>
2208
+ <div class="icon-grid-item">
2209
+ <pds-icon icon="download" size="lg"></pds-icon>
2210
+ <span class="icon-grid-label">download</span>
2211
+ </div>
2212
+ </div>
2213
+ </section>
2214
+
2215
+ <!-- Layout & Cards Section -->
2216
+ <section class="showcase-section alt-bg">
2217
+ <h2>
2218
+ <pds-icon
2219
+ icon="desktop"
2220
+ size="lg"
2221
+ class="icon-primary"
2222
+ ></pds-icon>
2223
+ Layout & Cards
2224
+ </h2>
2225
+
2226
+ <div class="grid grid-cols-1">
2227
+ <div class="card surface-elevated">
2228
+ <h3>Elevated Surface</h3>
2229
+ <p>This surface has a subtle shadow and elevated background.</p>
2230
+ </div>
2231
+
2232
+ <div class="grid grid-cols-3">
2233
+ <div class="card">
2234
+ <h4>Card 1</h4>
2235
+ <p>Cards provide a clean container for content.</p>
2236
+ </div>
2237
+ <div class="card">
2238
+ <h4>Card 2</h4>
2239
+ <p>They have consistent spacing and shadows.</p>
2240
+ </div>
2241
+ <div class="card">
2242
+ <h4>Card 3</h4>
2243
+ <p>And they respond beautifully to hover states.</p>
2244
+ </div>
2245
+ </div>
2246
+ </div>
2247
+
2248
+ <h3>Accordion Component</h3>
2249
+ <section class="accordion" aria-label="FAQ">
2250
+ <details>
2251
+ <summary id="q1">How billing works</summary>
2252
+ <div role="region" aria-labelledby="q1">
2253
+ <p>We charge monthly on the 1st…</p>
2254
+ </div>
2255
+ </details>
2256
+
2257
+ <details>
2258
+ <summary id="q2">Refund policy</summary>
2259
+ <div role="region" aria-labelledby="q2">
2260
+ <p>You can request a refund within 14 days…</p>
2261
+ </div>
2262
+ </details>
2263
+
2264
+ <details>
2265
+ <summary id="q3">Invoices</summary>
2266
+ <div role="region" aria-labelledby="q3">
2267
+ <p>Download invoices from Settings → Billing…</p>
2268
+ </div>
2269
+ </details>
2270
+ </section>
2271
+ </section>
2272
+
2273
+ <section class="showcase-section">
2274
+ <h2>
2275
+ <pds-icon icon="list" size="lg" class="icon-primary"></pds-icon>
2276
+ Tables
2277
+ </h2>
2278
+
2279
+ <h3>Default Table</h3>
2280
+ <table>
2281
+ <thead>
2282
+ <tr>
2283
+ <th>Name</th>
2284
+ <th>Role</th>
2285
+ <th>Department</th>
2286
+ <th>Status</th>
2287
+ </tr>
2288
+ </thead>
2289
+ <tbody>
2290
+ <tr>
2291
+ <td>Alice Johnson</td>
2292
+ <td>Senior Developer</td>
2293
+ <td>Engineering</td>
2294
+ <td><span class="badge badge-success">Active</span></td>
2295
+ </tr>
2296
+ <tr>
2297
+ <td>Bob Smith</td>
2298
+ <td>Product Manager</td>
2299
+ <td>Product</td>
2300
+ <td><span class="badge badge-success">Active</span></td>
2301
+ </tr>
2302
+ <tr>
2303
+ <td>Carol Williams</td>
2304
+ <td>UX Designer</td>
2305
+ <td>Design</td>
2306
+ <td><span class="badge badge-warning">Away</span></td>
2307
+ </tr>
2308
+ <tr>
2309
+ <td>David Brown</td>
2310
+ <td>DevOps Engineer</td>
2311
+ <td>Engineering</td>
2312
+ <td><span class="badge badge-danger">Offline</span></td>
2313
+ </tr>
2314
+ </tbody>
2315
+ </table>
2316
+
2317
+ <h3>Striped Table</h3>
2318
+ <table class="table-striped">
2319
+ <thead>
2320
+ <tr>
2321
+ <th>Product</th>
2322
+ <th>Price</th>
2323
+ <th>Stock</th>
2324
+ <th>Category</th>
2325
+ </tr>
2326
+ </thead>
2327
+ <tbody>
2328
+ <tr>
2329
+ <td>Laptop Pro</td>
2330
+ <td>$1,299</td>
2331
+ <td>45</td>
2332
+ <td>Electronics</td>
2333
+ </tr>
2334
+ <tr>
2335
+ <td>Wireless Mouse</td>
2336
+ <td>$29</td>
2337
+ <td>128</td>
2338
+ <td>Accessories</td>
2339
+ </tr>
2340
+ <tr>
2341
+ <td>USB-C Hub</td>
2342
+ <td>$59</td>
2343
+ <td>76</td>
2344
+ <td>Accessories</td>
2345
+ </tr>
2346
+ <tr>
2347
+ <td>Monitor 27"</td>
2348
+ <td>$449</td>
2349
+ <td>23</td>
2350
+ <td>Electronics</td>
2351
+ </tr>
2352
+ </tbody>
2353
+ </table>
2354
+
2355
+ <h3>Bordered Compact Table</h3>
2356
+ <table class="table-bordered table-compact">
2357
+ <thead>
2358
+ <tr>
2359
+ <th>ID</th>
2360
+ <th>Task</th>
2361
+ <th>Priority</th>
2362
+ <th>Due Date</th>
2363
+ </tr>
2364
+ </thead>
2365
+ <tbody>
2366
+ <tr>
2367
+ <td>#101</td>
2368
+ <td>Fix navigation bug</td>
2369
+ <td><span class="badge badge-danger">High</span></td>
2370
+ <td>Oct 15, 2025</td>
2371
+ </tr>
2372
+ <tr>
2373
+ <td>#102</td>
2374
+ <td>Update documentation</td>
2375
+ <td><span class="badge badge-warning">Medium</span></td>
2376
+ <td>Oct 18, 2025</td>
2377
+ </tr>
2378
+ <tr>
2379
+ <td>#103</td>
2380
+ <td>Refactor CSS</td>
2381
+ <td><span class="badge badge-info">Low</span></td>
2382
+ <td>Oct 25, 2025</td>
2383
+ </tr>
2384
+ </tbody>
2385
+ </table>
2386
+ </section>
2387
+
2388
+ <!-- Form Groups Section -->
2389
+ <section class="showcase-section alt-bg">
2390
+ <h2>
2391
+ <pds-icon
2392
+ icon="list-bullets"
2393
+ size="lg"
2394
+ class="icon-primary"
2395
+ ></pds-icon>
2396
+ Form Groups
2397
+ </h2>
2398
+
2399
+ <div class="grid grid-cols-2">
2400
+ <div class="form-group">
2401
+ <h3>Radio Buttons</h3>
2402
+ <fieldset role="radiogroup">
2403
+ <legend>Select your plan</legend>
2404
+ <label>
2405
+ <input type="radio" name="plan" value="free" checked />
2406
+ <span>Free - $0/month</span>
2407
+ </label>
2408
+ <label>
2409
+ <input type="radio" name="plan" value="pro" />
2410
+ <span>Pro - $29/month</span>
2411
+ </label>
2412
+ <label>
2413
+ <input type="radio" name="plan" value="enterprise" />
2414
+ <span>Enterprise - $99/month</span>
2415
+ </label>
2416
+ </fieldset>
2417
+ </div>
2418
+
2419
+ <div class="form-group">
2420
+ <h3>Checkboxes</h3>
2421
+ <fieldset role="group">
2422
+ <legend>Select features</legend>
2423
+ <label>
2424
+ <input
2425
+ type="checkbox"
2426
+ name="features"
2427
+ value="api"
2428
+ checked
2429
+ />
2430
+ <span>API Access</span>
2431
+ </label>
2432
+ <label>
2433
+ <input
2434
+ type="checkbox"
2435
+ name="features"
2436
+ value="analytics"
2437
+ checked
2438
+ />
2439
+ <span>Advanced Analytics</span>
2440
+ </label>
2441
+ <label>
2442
+ <input type="checkbox" name="features" value="support" />
2443
+ <span>Priority Support</span>
2444
+ </label>
2445
+ <label>
2446
+ <input type="checkbox" name="features" value="sso" />
2447
+ <span>Single Sign-On</span>
2448
+ </label>
2449
+ </fieldset>
2450
+ </div>
2451
+ </div>
2452
+ </section>
2453
+
2454
+ <!-- Smart Surfaces Section -->
2455
+ <section class="showcase-section" data-section="smart-surfaces">
2456
+ <h2>
2457
+ <pds-icon
2458
+ icon="palette"
2459
+ size="lg"
2460
+ class="icon-primary"
2461
+ ></pds-icon>
2462
+ Smart Surface System
2463
+ </h2>
2464
+
2465
+ <p>
2466
+ The smart surface system automatically adapts text, icon, shadow,
2467
+ and border colors based on surface backgrounds. All colors
2468
+ maintain WCAG AA contrast ratios automatically.
2469
+ </p>
2470
+
2471
+ <h3>Surface Variants</h3>
2472
+ <div class="surface-demo-grid">
2473
+ <div class="surface-base" style="padding: var(--spacing-6);">
2474
+ <strong class="surface-title">
2475
+ <pds-icon icon="square"></pds-icon>
2476
+ Base Surface
2477
+ </strong>
2478
+ <p class="surface-description">
2479
+ Default background with auto-adjusted text and icons
2480
+ </p>
2481
+ <button
2482
+ class="btn-primary"
2483
+ style="margin-top: var(--spacing-3);"
2484
+ >
2485
+ Button
2486
+ </button>
2487
+ </div>
2488
+ <div class="surface-subtle" style="padding: var(--spacing-6);">
2489
+ <strong class="surface-title">
2490
+ <pds-icon icon="square"></pds-icon>
2491
+ Subtle Surface
2492
+ </strong>
2493
+ <p class="surface-description">
2494
+ Slightly different tone for visual hierarchy
2495
+ </p>
2496
+ <button
2497
+ class="btn-secondary"
2498
+ style="margin-top: var(--spacing-3);"
2499
+ >
2500
+ Button
2501
+ </button>
2502
+ </div>
2503
+ <div class="surface-elevated" style="padding: var(--spacing-6);">
2504
+ <strong class="surface-title">
2505
+ <pds-icon icon="arrow-up"></pds-icon>
2506
+ Elevated Surface
2507
+ </strong>
2508
+ <p class="surface-description">
2509
+ Raised with smart shadows that adapt in dark mode
2510
+ </p>
2511
+ <button
2512
+ class="btn-primary"
2513
+ style="margin-top: var(--spacing-3);"
2514
+ >
2515
+ Button
2516
+ </button>
2517
+ </div>
2518
+ <div class="surface-overlay" style="padding: var(--spacing-6);">
2519
+ <strong class="surface-title">
2520
+ <pds-icon icon="desktop"></pds-icon>
2521
+ Overlay Surface
2522
+ </strong>
2523
+ <p class="surface-description">
2524
+ Modal/dropdown backgrounds with stronger shadows
2525
+ </p>
2526
+ <button
2527
+ class="btn-outline"
2528
+ style="margin-top: var(--spacing-3);"
2529
+ >
2530
+ Button
2531
+ </button>
2532
+ </div>
2533
+ </div>
2534
+
2535
+ <h3>Context-Aware Shadows</h3>
2536
+ <p>
2537
+ Shadows automatically invert in dark mode: dark shadows on light
2538
+ surfaces, light shadows on dark surfaces.
2539
+ </p>
2540
+ <div class="shadow-demo-grid">
2541
+ <div class="shadow-demo-item shadow-sm">
2542
+ <pds-icon icon="feather" size="lg"></pds-icon>
2543
+ <strong>Small</strong>
2544
+ <p>--shadow-sm</p>
2545
+ </div>
2546
+ <div class="shadow-demo-item shadow-md">
2547
+ <pds-icon icon="grid-four" size="lg"></pds-icon>
2548
+ <strong>Medium</strong>
2549
+ <p>--shadow-md</p>
2550
+ </div>
2551
+ <div class="shadow-demo-item shadow-lg">
2552
+ <pds-icon icon="rocket" size="lg"></pds-icon>
2553
+ <strong>Large</strong>
2554
+ <p>--shadow-lg</p>
2555
+ </div>
2556
+ </div>
2557
+
2558
+ <h3>Surface Borders</h3>
2559
+ <article class="card border-gradient">
2560
+ <p>A card with a border gradient</p>
2561
+ </article>
2562
+
2563
+ <article class="card border-gradient-glow">
2564
+ <p>A card with a glowing border gradient</p>
2565
+ </article>
2566
+ </section>
2567
+
2568
+ <!-- Nested Surfaces Section -->
2569
+ <section
2570
+ class="showcase-section alt-bg"
2571
+ data-section="nested-surfaces"
2572
+ >
2573
+ <h2>
2574
+ <pds-icon
2575
+ icon="grid-four"
2576
+ size="lg"
2577
+ class="icon-primary"
2578
+ ></pds-icon>
2579
+ Nested Surfaces
2580
+ </h2>
2581
+
2582
+ <p>
2583
+ Surfaces can be nested infinitely. Each level maintains proper
2584
+ contrast and visual hierarchy automatically.
2585
+ </p>
2586
+
2587
+ <div class="surface-base" style="padding: var(--spacing-6);">
2588
+ <h4>
2589
+ <pds-icon icon="circle"></pds-icon>
2590
+ Level 1: Base Surface
2591
+ </h4>
2592
+ <p>
2593
+ Notice how icons and text adapt at each nesting level to
2594
+ maintain readability.
2595
+ </p>
2596
+
2597
+ <div
2598
+ class="surface-elevated"
2599
+ style="padding: var(--spacing-6); margin-top: var(--spacing-4);"
2600
+ >
2601
+ <h5>
2602
+ <pds-icon icon="arrow-right"></pds-icon>
2603
+ Level 2: Elevated Surface
2604
+ </h5>
2605
+ <p>Shadows and text colors automatically adjust</p>
2606
+
2607
+ <div
2608
+ class="grid grid-cols-2"
2609
+ style="margin-top: var(--spacing-4);"
2610
+ >
2611
+ <div class="card">
2612
+ <h6>
2613
+ <pds-icon icon="check"></pds-icon>
2614
+ Level 3: Card
2615
+ </h6>
2616
+ <p>Perfect contrast maintained</p>
2617
+
2618
+ <div
2619
+ class="surface-sunken"
2620
+ style="padding: var(--spacing-4); margin-top: var(--spacing-3);"
2621
+ >
2622
+ <small>
2623
+ <pds-icon icon="info" size="sm"></pds-icon>
2624
+ Level 4: Sunken surface inside card
2625
+ </small>
2626
+ </div>
2627
+ </div>
2628
+
2629
+ <div class="card">
2630
+ <h6>
2631
+ <pds-icon icon="star"></pds-icon>
2632
+ Another Card
2633
+ </h6>
2634
+ <p>All elements adapt automatically</p>
2635
+ <button
2636
+ class="btn-primary btn-sm"
2637
+ style="margin-top: var(--spacing-2);"
2638
+ >
2639
+ <pds-icon icon="heart" size="sm"></pds-icon>
2640
+ Action
2641
+ </button>
2642
+ </div>
2643
+ </div>
2644
+ </div>
2645
+ </div>
2646
+
2647
+ <h3>Card Grids</h3>
2648
+ <p>Cards automatically adapt when nested or grouped</p>
2649
+ <div class="grid grid-cols-3">
2650
+ <div class="card">
2651
+ <h5>
2652
+ <pds-icon icon="palette"></pds-icon>
2653
+ Design
2654
+ </h5>
2655
+ <p>Smart surfaces handle theming automatically</p>
2656
+ </div>
2657
+ <div class="card">
2658
+ <h5>
2659
+ <pds-icon icon="code"></pds-icon>
2660
+ Development
2661
+ </h5>
2662
+ <p>Zero manual color overrides needed</p>
2663
+ </div>
2664
+ <div class="card">
2665
+ <h5>
2666
+ <pds-icon icon="rocket"></pds-icon>
2667
+ Performance
2668
+ </h5>
2669
+ <p>CSS custom properties are fast</p>
2670
+ </div>
2671
+ </div>
2672
+ </section>
2673
+
2674
+ <!-- Surface Inversion Section -->
2675
+ <section class="showcase-section" data-section="surface-inversion">
2676
+ <h2>
2677
+ <pds-icon icon="moon" size="lg" class="icon-primary"></pds-icon>
2678
+ Surface Inversion
2679
+ </h2>
2680
+
2681
+ <p>
2682
+ The smart surface system automatically inverts text and icon
2683
+ colors when you use a ${inversePhrase} (or vice versa). Toggle
2684
+ dark mode to see the magic!
2685
+ </p>
2686
+
2687
+ <h3>${inversionTitle}</h3>
2688
+ <div class="grid grid-cols-2">
2689
+ <div class="demo-inversion-box surface-inverse surface-box">
2690
+ <h4>
2691
+ <pds-icon icon="moon"></pds-icon>
2692
+ Automatic Inversion
2693
+ </h4>
2694
+ <p>
2695
+ This dark surface automatically uses light text and icons for
2696
+ perfect readability
2697
+ </p>
2698
+ <button
2699
+ class="btn-primary"
2700
+ style="margin-top: var(--spacing-3);"
2701
+ >
2702
+ Primary Button
2703
+ </button>
2704
+ </div>
2705
+
2706
+ <div class="demo-inversion-box surface-overlay surface-box">
2707
+ <h4>
2708
+ <pds-icon icon="palette"></pds-icon>
2709
+ Overlay Surface
2710
+ </h4>
2711
+ <p>Text and icons auto-adapt to maintain WCAG AA contrast</p>
2712
+ <button
2713
+ class="btn-secondary"
2714
+ style="margin-top: var(--spacing-3);"
2715
+ >
2716
+ Secondary Button
2717
+ </button>
2718
+ </div>
2719
+ </div>
2720
+
2721
+ <h3>Semantic Surfaces with Auto-Contrast</h3>
2722
+ <div class="grid grid-cols-3">
2723
+ <div
2724
+ class="demo-inversion-box alert alert-success surface-center"
2725
+ >
2726
+ <pds-icon icon="check-circle" size="xl"></pds-icon>
2727
+ <h5 style="margin-top: var(--spacing-2);">Success</h5>
2728
+ <p>Icons remain visible</p>
2729
+ </div>
2730
+
2731
+ <div
2732
+ class="demo-inversion-box alert alert-warning surface-center"
2733
+ >
2734
+ <pds-icon icon="warning" size="xl"></pds-icon>
2735
+ <h5 style="margin-top: var(--spacing-2);">Warning</h5>
2736
+ <p>Perfect contrast maintained</p>
2737
+ </div>
2738
+
2739
+ <div class="demo-inversion-box alert alert-danger surface-center">
2740
+ <pds-icon icon="heart" size="xl"></pds-icon>
2741
+ <h5 style="margin-top: var(--spacing-2);">Danger</h5>
2742
+ <p>Automatic adjustment</p>
2743
+ </div>
2744
+ </div>
2745
+ </section>
2746
+
2747
+ <!-- Grid Utilities Section -->
2748
+ <section class="showcase-section" data-section="grid-utilities">
2749
+ <h2>
2750
+ <pds-icon
2751
+ icon="squares-four"
2752
+ size="lg"
2753
+ class="icon-primary"
2754
+ ></pds-icon>
2755
+ Grid Utilities
2756
+ </h2>
2757
+ <p>
2758
+ Modern, config-driven grid system with auto-fit responsive
2759
+ layouts. All utilities are generated from
2760
+ <code>layout.gridSystem</code> configuration.
2761
+ </p>
2762
+
2763
+ <h3>Fixed Column Grids</h3>
2764
+ <p>
2765
+ Use <code>.grid-cols-{n}</code> classes for fixed column layouts:
2766
+ </p>
2767
+
2768
+ <div
2769
+ class="grid grid-cols-2 gap-md"
2770
+ style="margin-bottom: var(--spacing-4);"
2771
+ >
2772
+ <div class="card">
2773
+ <pds-icon
2774
+ icon="square"
2775
+ size="lg"
2776
+ class="icon-primary"
2777
+ ></pds-icon>
2778
+ <h4>Grid Column 1</h4>
2779
+ <p>Two column layout</p>
2780
+ </div>
2781
+ <div class="card">
2782
+ <pds-icon
2783
+ icon="square"
2784
+ size="lg"
2785
+ class="icon-secondary"
2786
+ ></pds-icon>
2787
+ <h4>Grid Column 2</h4>
2788
+ <p>Equal width columns</p>
2789
+ </div>
2790
+ </div>
2791
+
2792
+ <div
2793
+ class="grid grid-cols-3 gap-sm"
2794
+ style="margin-bottom: var(--spacing-4);"
2795
+ >
2796
+ <div class="card">
2797
+ <pds-icon
2798
+ icon="circle"
2799
+ size="md"
2800
+ class="icon-success"
2801
+ ></pds-icon>
2802
+ <p>Column 1</p>
2803
+ </div>
2804
+ <div class="card">
2805
+ <pds-icon
2806
+ icon="circle"
2807
+ size="md"
2808
+ class="icon-warning"
2809
+ ></pds-icon>
2810
+ <p>Column 2</p>
2811
+ </div>
2812
+ <div class="card">
2813
+ <pds-icon icon="circle" size="md" class="icon-error"></pds-icon>
2814
+ <p>Column 3</p>
2815
+ </div>
2816
+ </div>
2817
+
2818
+ <div class="grid grid-cols-4 gap-xs">
2819
+ <div class="card"><p>1</p></div>
2820
+ <div class="card"><p>2</p></div>
2821
+ <div class="card"><p>3</p></div>
2822
+ <div class="card"><p>4</p></div>
2823
+ </div>
2824
+
2825
+ <h3>Auto-Fit Responsive Grids</h3>
2826
+ <p>
2827
+ Use <code>.grid-auto-{size}</code> for responsive layouts that
2828
+ automatically adjust columns based on available space:
2829
+ </p>
2830
+
2831
+ <h4><code>.grid-auto-sm</code> (min 150px)</h4>
2832
+ <div
2833
+ class="grid grid-auto-sm gap-md"
2834
+ style="margin-bottom: var(--spacing-4);"
2835
+ >
2836
+ <div class="card">
2837
+ <pds-icon icon="desktop" size="lg" class="icon-info"></pds-icon>
2838
+ <h5>Responsive</h5>
2839
+ <p>Automatically wraps</p>
2840
+ </div>
2841
+ <div class="card">
2842
+ <pds-icon
2843
+ icon="device-mobile"
2844
+ size="lg"
2845
+ class="icon-info"
2846
+ ></pds-icon>
2847
+ <h5>Adaptive</h5>
2848
+ <p>Based on space</p>
2849
+ </div>
2850
+ <div class="card">
2851
+ <pds-icon icon="globe" size="lg" class="icon-info"></pds-icon>
2852
+ <h5>Flexible</h5>
2853
+ <p>Resize the window</p>
2854
+ </div>
2855
+ <div class="card">
2856
+ <pds-icon icon="feather" size="lg" class="icon-info"></pds-icon>
2857
+ <h5>Dynamic</h5>
2858
+ <p>No breakpoints needed</p>
2859
+ </div>
2860
+ </div>
2861
+
2862
+ <h4><code>.grid-auto-md</code> (min 250px)</h4>
2863
+ <div
2864
+ class="grid grid-auto-md gap-lg"
2865
+ style="margin-bottom: var(--spacing-4);"
2866
+ >
2867
+ <div class="card surface-elevated">
2868
+ <pds-icon
2869
+ icon="rocket"
2870
+ size="xl"
2871
+ class="icon-accent"
2872
+ ></pds-icon>
2873
+ <h5>Card 1</h5>
2874
+ <p>Larger minimum width means fewer columns on small screens</p>
2875
+ </div>
2876
+ <div class="card surface-elevated">
2877
+ <pds-icon
2878
+ icon="palette"
2879
+ size="xl"
2880
+ class="icon-accent"
2881
+ ></pds-icon>
2882
+ <h5>Card 2</h5>
2883
+ <p>Smart surface tokens apply automatically</p>
2884
+ </div>
2885
+ <div class="card surface-elevated">
2886
+ <pds-icon icon="heart" size="xl" class="icon-accent"></pds-icon>
2887
+ <h5>Card 3</h5>
2888
+ <p>Consistent spacing with gap utilities</p>
2889
+ </div>
2890
+ </div>
2891
+
2892
+ <h3>Gap Utilities</h3>
2893
+ <p>
2894
+ Control spacing between grid items with
2895
+ <code>.gap-{size}</code> classes:
2896
+ </p>
2897
+
2898
+ <div
2899
+ style="display: grid; gap: var(--spacing-4); grid-template-columns: 1fr 1fr;"
2900
+ >
2901
+ <div>
2902
+ <p><strong>.gap-xs</strong> (spacing-1)</p>
2903
+ <div class="grid grid-cols-3 gap-xs">
2904
+ <div class="card"><p>A</p></div>
2905
+ <div class="card"><p>B</p></div>
2906
+ <div class="card"><p>C</p></div>
2907
+ </div>
2908
+ </div>
2909
+
2910
+ <div>
2911
+ <p><strong>.gap-sm</strong> (spacing-2)</p>
2912
+ <div class="grid grid-cols-3 gap-sm">
2913
+ <div class="card"><p>A</p></div>
2914
+ <div class="card"><p>B</p></div>
2915
+ <div class="card"><p>C</p></div>
2916
+ </div>
2917
+ </div>
2918
+
2919
+ <div>
2920
+ <p><strong>.gap-md</strong> (spacing-4)</p>
2921
+ <div class="grid grid-cols-3 gap-md">
2922
+ <div class="card"><p>A</p></div>
2923
+ <div class="card"><p>B</p></div>
2924
+ <div class="card"><p>C</p></div>
2925
+ </div>
2926
+ </div>
2927
+
2928
+ <div>
2929
+ <p><strong>.gap-lg</strong> (spacing-6)</p>
2930
+ <div class="grid grid-cols-3 gap-lg">
2931
+ <div class="card"><p>A</p></div>
2932
+ <div class="card"><p>B</p></div>
2933
+ <div class="card"><p>C</p></div>
2934
+ </div>
2935
+ </div>
2936
+ </div>
2937
+
2938
+ <h3>Code Inspector Support</h3>
2939
+ <p class="interactive-demo">
2940
+ <pds-icon
2941
+ icon="cursor-click"
2942
+ size="sm"
2943
+ class="icon-primary"
2944
+ ></pds-icon>
2945
+ Enable the <strong>Code Inspector</strong> and click on any grid
2946
+ container above. The ontology now recognizes layout patterns like
2947
+ <code>grid</code>, <code>grid-cols</code>, and
2948
+ <code>grid-auto</code> for intelligent component detection.
2949
+ </p>
2950
+ </section>
2951
+
2952
+ <!-- Mesh Gradients Section -->
2953
+ <section class="showcase-section" data-section="mesh-gradients">
2954
+ <h2>
2955
+ <pds-icon
2956
+ icon="palette"
2957
+ size="lg"
2958
+ class="icon-primary"
2959
+ ></pds-icon>
2960
+ Mesh Gradients
2961
+ </h2>
2962
+ <p>
2963
+ Subtle, beautiful mesh gradient backgrounds generated from your
2964
+ color palette. Using <code>--background-mesh-01</code> through
2965
+ <code>--background-mesh-05</code> custom properties. Automatically
2966
+ adapts to light and dark modes.
2967
+ </p>
2968
+
2969
+ <div
2970
+ class="grid grid-cols-2 gap-lg"
2971
+ style="margin-bottom: var(--spacing-6);"
2972
+ >
2973
+ <div
2974
+ style="position: relative; background: var(--background-mesh-01); padding: var(--spacing-6); border-radius: var(--radius-lg); min-height: 200px; display: flex; align-items: center; justify-content: center; border: 1px solid var(--color-border);"
2975
+ >
2976
+ <button
2977
+ class="btn-primary btn-xs"
2978
+ style="position: absolute; top: var(--spacing-2); right: var(--spacing-2);"
2979
+ @pointerdown=${() => this.previewMesh("01")}
2980
+ @pointerup=${this.clearMeshPreview}
2981
+ @pointerleave=${this.clearMeshPreview}
2982
+ title="Press and hold to preview on page background"
2983
+ >
2984
+ <pds-icon icon="eye" size="sm"></pds-icon>
2985
+ Preview
2986
+ </button>
2987
+ <div
2988
+ style="background: var(--color-surface-base); padding: var(--spacing-4); border-radius: var(--radius-md); box-shadow: var(--shadow-md);"
2989
+ >
2990
+ <h4 style="margin: 0;">Mesh 01</h4>
2991
+ <p style="margin: var(--spacing-2) 0 0 0; opacity: 0.7;">
2992
+ Subtle radial blend
2993
+ </p>
2994
+ </div>
2995
+ </div>
2996
+ <div
2997
+ style="position: relative; background: var(--background-mesh-02); padding: var(--spacing-6); border-radius: var(--radius-lg); min-height: 200px; display: flex; align-items: center; justify-content: center; border: 1px solid var(--color-border);"
2998
+ >
2999
+ <button
3000
+ class="btn-primary btn-xs"
3001
+ style="position: absolute; top: var(--spacing-2); right: var(--spacing-2);"
3002
+ @pointerdown=${() => this.previewMesh("02")}
3003
+ @pointerup=${this.clearMeshPreview}
3004
+ @pointerleave=${this.clearMeshPreview}
3005
+ title="Press and hold to preview on page background"
3006
+ >
3007
+ <pds-icon icon="eye" size="sm"></pds-icon>
3008
+ Preview
3009
+ </button>
3010
+ <div
3011
+ style="background: var(--color-surface-base); padding: var(--spacing-4); border-radius: var(--radius-md); box-shadow: var(--shadow-md);"
3012
+ >
3013
+ <h4 style="margin: 0;">Mesh 02</h4>
3014
+ <p style="margin: var(--spacing-2) 0 0 0; opacity: 0.7;">
3015
+ Corner accents
3016
+ </p>
3017
+ </div>
3018
+ </div>
3019
+ </div>
3020
+
3021
+ <div class="grid grid-cols-3 gap-md">
3022
+ <div
3023
+ style="position: relative; background: var(--background-mesh-03); padding: var(--spacing-5); border-radius: var(--radius-md); min-height: 150px; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; border: 1px solid var(--color-border);"
3024
+ >
3025
+ <button
3026
+ class="btn-primary btn-xs"
3027
+ style="position: absolute; top: var(--spacing-2); right: var(--spacing-2);"
3028
+ @pointerdown=${() => this.previewMesh("03")}
3029
+ @pointerup=${this.clearMeshPreview}
3030
+ @pointerleave=${this.clearMeshPreview}
3031
+ title="Press and hold to preview on page background"
3032
+ >
3033
+ <pds-icon icon="eye" size="sm"></pds-icon>
3034
+ </button>
3035
+ <pds-icon
3036
+ icon="sparkle"
3037
+ size="xl"
3038
+ style="opacity: 0.9; margin-bottom: var(--spacing-2);"
3039
+ ></pds-icon>
3040
+ <code style="font-size: 0.75rem;">mesh-03</code>
3041
+ </div>
3042
+ <div
3043
+ style="position: relative; background: var(--background-mesh-04); padding: var(--spacing-5); border-radius: var(--radius-md); min-height: 150px; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; border: 1px solid var(--color-border);"
3044
+ >
3045
+ <button
3046
+ class="btn-primary btn-xs"
3047
+ style="position: absolute; top: var(--spacing-2); right: var(--spacing-2);"
3048
+ @pointerdown=${() => this.previewMesh("04")}
3049
+ @pointerup=${this.clearMeshPreview}
3050
+ @pointerleave=${this.clearMeshPreview}
3051
+ title="Press and hold to preview on page background"
3052
+ >
3053
+ <pds-icon icon="eye" size="sm"></pds-icon>
3054
+ </button>
3055
+ <pds-icon
3056
+ icon="sparkle"
3057
+ size="xl"
3058
+ style="opacity: 0.9; margin-bottom: var(--spacing-2);"
3059
+ ></pds-icon>
3060
+ <code style="font-size: 0.75rem;">mesh-04</code>
3061
+ </div>
3062
+ <div
3063
+ style="position: relative; background: var(--background-mesh-05); padding: var(--spacing-5); border-radius: var(--radius-md); min-height: 150px; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; border: 1px solid var(--color-border);"
3064
+ >
3065
+ <button
3066
+ class="btn-primary btn-xs"
3067
+ style="position: absolute; top: var(--spacing-2); right: var(--spacing-2);"
3068
+ @pointerdown=${() => this.previewMesh("05")}
3069
+ @pointerup=${this.clearMeshPreview}
3070
+ @pointerleave=${this.clearMeshPreview}
3071
+ title="Press and hold to preview on page background"
3072
+ >
3073
+ <pds-icon icon="eye" size="sm"></pds-icon>
3074
+ </button>
3075
+ <pds-icon
3076
+ icon="sparkle"
3077
+ size="xl"
3078
+ style="opacity: 0.9; margin-bottom: var(--spacing-2);"
3079
+ ></pds-icon>
3080
+ <code style="font-size: 0.75rem;">mesh-05</code>
3081
+ </div>
3082
+ </div>
3083
+
3084
+ <h3>Usage</h3>
3085
+ <pre
3086
+ class="code-block"
3087
+ style="margin-top: var(--spacing-4);"
3088
+ ><code class="language-css">/* Apply as background */
3089
+ .hero-section {
3090
+ background: var(--background-mesh-01);
3091
+ }
3092
+
3093
+ /* Combine with surface colors */
3094
+ .card {
3095
+ background: var(--background-mesh-03);
3096
+ backdrop-filter: blur(10px);
3097
+ }
3098
+
3099
+ /* Layer over solid colors */
3100
+ .container {
3101
+ background-color: var(--color-surface-base);
3102
+ background-image: var(--background-mesh-02);
3103
+ }</code></pre>
3104
+
3105
+ <p class="interactive-demo" style="margin-top: var(--spacing-4);">
3106
+ <pds-icon
3107
+ icon="moon-stars"
3108
+ size="sm"
3109
+ class="icon-primary"
3110
+ ></pds-icon>
3111
+ Toggle between light and dark modes to see how mesh gradients
3112
+ automatically adapt with reduced opacity in dark mode for subtle,
3113
+ non-interfering backgrounds.
3114
+ </p>
3115
+ </section>
3116
+
3117
+ <!-- Interactive States Section -->
3118
+ <section
3119
+ class="showcase-section alt-bg"
3120
+ data-section="interactive-states"
3121
+ >
3122
+ <h2>
3123
+ <pds-icon
3124
+ icon="cursor-click"
3125
+ size="lg"
3126
+ class="icon-primary"
3127
+ ></pds-icon>
3128
+ Interactive States
3129
+ </h2>
3130
+
3131
+ <h3>Focus States</h3>
3132
+ <p class="interactive-demo">
3133
+ Press Tab to navigate and see focus rings on interactive elements:
3134
+ </p>
3135
+ <div class="flex-wrap">
3136
+ <button class="btn-primary">Button 1</button>
3137
+ <button class="btn-secondary">Button 2</button>
3138
+ <input type="text" placeholder="Focus me" />
3139
+ <select>
3140
+ <option>Option 1</option>
3141
+ <option>Option 2</option>
3142
+ </select>
3143
+ <a href="#">Link</a>
3144
+ </div>
3145
+
3146
+ <h3>Transition Speeds</h3>
3147
+ <p class="interactive-demo">
3148
+ Current setting:
3149
+ <strong
3150
+ >${this.config?.behavior?.transitionSpeed || "normal"}</strong
3151
+ >
3152
+ <br />
3153
+ Click the button to see the transition in action:
3154
+ </p>
3155
+
3156
+ <div class="transition-speed-demo">
3157
+ <button
3158
+ class="btn-primary"
3159
+ @click="${this.triggerTransitionDemo}"
3160
+ >
3161
+ <pds-icon icon="play" size="sm"></pds-icon>
3162
+ Animate Transition
3163
+ </button>
3164
+
3165
+ <div class="transition-demo-stage">
3166
+ <div class="transition-demo-ball" id="transition-ball">
3167
+ <pds-icon icon="cursor-click" size="lg"></pds-icon>
3168
+ </div>
3169
+ </div>
3170
+ </div>
3171
+
3172
+ <p class="interactive-demo" style="margin-top: var(--spacing-4);">
3173
+ Change the <em>Transition Speed</em> setting in the designer panel
3174
+ to see how it affects the animation.
3175
+ </p>
3176
+ </section>
3177
+
3178
+ <!-- Toast Notifications Section -->
3179
+ <section class="showcase-section">
3180
+ <h2>
3181
+ <pds-icon
3182
+ icon="bell-ringing"
3183
+ size="lg"
3184
+ class="icon-primary"
3185
+ ></pds-icon>
3186
+ Toast Notifications
3187
+ </h2>
3188
+
3189
+ <p class="toast-demo-description">
3190
+ Toast notifications appear in the top-right corner and
3191
+ auto-dismiss after a few seconds. Click the buttons below to see
3192
+ them in action:
3193
+ </p>
3194
+
3195
+ <div class="flex flex-wrap gap-md">
3196
+ <button
3197
+ class="btn-primary btn-sm"
3198
+ @click="${this.showSuccessToast}"
3199
+ >
3200
+ <pds-icon icon="check-circle" size="sm"></pds-icon>
3201
+ Success
3202
+ </button>
3203
+ <button
3204
+ class="btn-secondary btn-sm"
3205
+ @click="${this.showInfoToast}"
3206
+ >
3207
+ <pds-icon icon="info" size="sm"></pds-icon>
3208
+ Info
3209
+ </button>
3210
+ <button
3211
+ class="btn-warning btn-sm"
3212
+ @click="${this.showWarningToast}"
3213
+ >
3214
+ <pds-icon icon="warning" size="sm"></pds-icon>
3215
+ Warning
3216
+ </button>
3217
+ <button class="btn-danger btn-sm" @click="${this.showErrorToast}">
3218
+ <pds-icon icon="x-circle" size="sm"></pds-icon>
3219
+ Error
3220
+ </button>
3221
+ <button class="btn-outline btn-sm" @click="${this.showLongToast}">
3222
+ <pds-icon icon="clock" size="sm"></pds-icon>
3223
+ Long
3224
+ </button>
3225
+ <button
3226
+ class="btn-outline btn-sm"
3227
+ @click="${this.showPersistentToast}"
3228
+ >
3229
+ <pds-icon icon="bell" size="sm"></pds-icon>
3230
+ Persistent
3231
+ </button>
3232
+ </div>
3233
+ </section>
3234
+
3235
+ <!-- Tab Strip Section -->
3236
+ <section class="showcase-section alt-bg" data-section="tabs">
3237
+ <h2>
3238
+ <pds-icon icon="tabs"></pds-icon>
3239
+ Tab Strip
3240
+ </h2>
3241
+ <p>
3242
+ Accessible tab navigation with hash-based routing and keyboard
3243
+ support.
3244
+ </p>
3245
+
3246
+ <div style="margin-top: var(--spacing-6);">
3247
+ <pds-tabstrip @tabchange="${this.handleTabChange}" label="Example Tabs">
3248
+ <pds-tabpanel id="overview" label="Overview">
3249
+ <h3>Overview</h3>
3250
+ <p>
3251
+ This is the overview tab. Tab strips provide organized
3252
+ navigation between related content.
3253
+ </p>
3254
+ </pds-tabpanel>
3255
+
3256
+ <pds-tabpanel id="features" label="Features">
3257
+ <h3>Features</h3>
3258
+ <p>
3259
+ Tab strips are built with modern web components and include:
3260
+ </p>
3261
+ <ul>
3262
+ <li>
3263
+ <strong>Deep linking:</strong> Each tab has a unique URL
3264
+ hash
3265
+ </li>
3266
+ <li>
3267
+ <strong>Progressive enhancement:</strong> Works without
3268
+ JavaScript
3269
+ </li>
3270
+ <li>
3271
+ <strong>Responsive:</strong> Adapts to mobile and desktop
3272
+ </li>
3273
+ <li>
3274
+ <strong>Customizable:</strong> Style with CSS variables
3275
+ </li>
3276
+ </ul>
3277
+ </pds-tabpanel>
3278
+
3279
+ <pds-tabpanel id="usage" label="Usage">
3280
+ <h3>Usage</h3>
3281
+ <p>Simple markup example:</p>
3282
+ <pre><code>&lt;pds-tabstrip label="My Tabs"&gt;
3283
+ &lt;pds-tabpanel id="tab1" label="First Tab"&gt;
3284
+ Content for first tab
3285
+ &lt;/pds-tabpanel&gt;
3286
+ &lt;pds-tabpanel id="tab2" label="Second Tab"&gt;
3287
+ Content for second tab
3288
+ &lt;/pds-tabpanel&gt;
3289
+ &lt;/pds-tabstrip&gt;</code></pre>
3290
+ </pds-tabpanel>
3291
+
3292
+ <pds-tabpanel id="accessibility" label="Accessibility">
3293
+ <h3>Accessibility</h3>
3294
+ <p>Built with accessibility in mind:</p>
3295
+ <ul>
3296
+ <li><code>aria-label</code> on navigation</li>
3297
+ <li><code>aria-current</code> on active tab</li>
3298
+ <li><code>aria-controls</code> linking tabs to panels</li>
3299
+ <li><code>role="region"</code> on tab panels</li>
3300
+ <li>Keyboard navigation with arrow keys</li>
3301
+ <li>Focus management</li>
3302
+ </ul>
3303
+ </pds-tabpanel>
3304
+ </pds-tabstrip>
3305
+ </div>
3306
+ </section>
3307
+
3308
+ <h3>Calendar</h3>
3309
+ <section class="card surface" >
3310
+ <pds-calendar></pds-calendar>
3311
+ </section>
3312
+
3313
+
3314
+ <!-- Drawer Section -->
3315
+ <section class="showcase-section">
3316
+ <h2>
3317
+ <pds-icon
3318
+ icon="squares-four"
3319
+ size="lg"
3320
+ class="icon-primary"
3321
+ ></pds-icon>
3322
+ Drawer Example
3323
+ </h2>
3324
+ <p>Open the global drawer from different sides:</p>
3325
+ <div
3326
+ class="btn-group"
3327
+ style="gap: var(--spacing-3); flex-wrap: wrap;"
3328
+ >
3329
+ <button
3330
+ class="btn-primary"
3331
+ @click=${() => this.openDrawerInPos("bottom")}
3332
+ >
3333
+ <pds-icon icon="sidebar" rotate="-90" size="sm"></pds-icon>
3334
+ Bottom Drawer
3335
+ </button>
3336
+ <button
3337
+ class="btn-secondary"
3338
+ @click=${() => this.openDrawerInPos("left")}
3339
+ >
3340
+ <pds-icon icon="sidebar" size="sm"></pds-icon>
3341
+ Left Drawer
3342
+ </button>
3343
+ <button
3344
+ class="btn-secondary"
3345
+ @click=${() => this.openDrawerInPos("right")}
3346
+ >
3347
+ <pds-icon icon="sidebar" rotate="180" size="sm"></pds-icon>
3348
+ Right Drawer
3349
+ </button>
3350
+ <button
3351
+ class="btn-secondary"
3352
+ @click=${() => this.openDrawerInPos("top")}
3353
+ >
3354
+ <pds-icon icon="sidebar" rotate="90" size="sm"></pds-icon>
3355
+ Top Drawer
3356
+ </button>
3357
+ </div>
3358
+ </section>
3359
+ </div>
3360
+ `;
3361
+ }
3362
+
3363
+ renderDrawerContent() {
3364
+ return html`
3365
+ <figure class="media-figure">
3366
+ <img
3367
+ class="media-image"
3368
+ src="https://picsum.photos/800/600?random=1"
3369
+ alt="Random landscape"
3370
+ />
3371
+ <figcaption class="media-caption">
3372
+ <strong>Figure 1:</strong> A beautiful landscape demonstrating image
3373
+ handling in the design system.
3374
+ </figcaption>
3375
+ </figure>
3376
+ `;
3377
+ }
3378
+
3379
+ async showDoc(doc) {
3380
+ const url = `/${doc}`;
3381
+ try {
3382
+ const res = await fetch(url, { cache: "no-store" });
3383
+ const text = await res.text();
3384
+
3385
+ // If server returned HTML (SPA fallback), show an error message
3386
+ const trimmed = text.trim();
3387
+ let htmlContent;
3388
+ if (trimmed.startsWith("<")) {
3389
+ htmlContent = `<div class="docs-error">Failed to load README at ${url}. Ensure readme.md exists under public/</div>`;
3390
+ } else {
3391
+ try {
3392
+ const conv = await this.getShowdownConverter();
3393
+ htmlContent = conv
3394
+ ? conv.makeHtml(trimmed)
3395
+ : `<pre>${this.escapeHTML(trimmed)}</pre>`;
3396
+ } catch (err) {
3397
+ htmlContent = `<pre>${this.escapeHTML(trimmed)}</pre>`;
3398
+ }
3399
+ }
3400
+
3401
+ const drawer = document.getElementById("global-drawer");
3402
+ drawer.show(html`${unsafeHTML(htmlContent)}`, {
3403
+ header: html`<h3>PDS Documentation</h3>`,
3404
+ });
3405
+ } catch (err) {
3406
+ console.error("Error fetching README:", err);
3407
+ const toaster = document.getElementById("global-toaster");
3408
+ toaster.toast("Error loading docs. See console.", { type: "danger" });
3409
+ }
3410
+ }
3411
+
3412
+ handleTabChange(event) {
3413
+ toast(`Switched to tab: ${event.detail.newTab}`, { type: "info" });
3414
+ }
3415
+
3416
+ openDrawer() {
3417
+ const drawer = document.getElementById("global-drawer");
3418
+
3419
+ drawer.show(
3420
+ html`
3421
+ <figure class="media-figure">
3422
+ <img
3423
+ class="media-image"
3424
+ src="https://picsum.photos/800/600?random=1"
3425
+ alt="Random landscape"
3426
+ />
3427
+ <figcaption class="media-caption">
3428
+ <strong>Figure 1:</strong> A beautiful landscape demonstrating
3429
+ image handling in the design system.
3430
+ </figcaption>
3431
+ </figure>
3432
+ `,
3433
+ {
3434
+ header: html`<h3>Example Drawer</h3>`,
3435
+ minHeight: "300px",
3436
+ position: "bottom",
3437
+ }
3438
+ );
3439
+ }
3440
+
3441
+ openDrawerInPos(position) {
3442
+ const drawer = document.getElementById("global-drawer");
3443
+ if (drawer) {
3444
+ drawer.show(this.renderDrawerContent(), {
3445
+ header: html`<h3>
3446
+ Example Drawer
3447
+ (${position.charAt(0).toUpperCase() + position.slice(1)})
3448
+ </h3>`,
3449
+ position: position,
3450
+ });
3451
+ }
3452
+ }
3453
+
3454
+ // Mesh gradient preview methods
3455
+ previewMesh(meshNumber) {
3456
+ const originalBg = document.body.style.background;
3457
+ this._originalBodyBg = originalBg;
3458
+ document.body.style.background = `var(--background-mesh-${meshNumber})`;
3459
+ document.body.style.backgroundAttachment = "fixed";
3460
+
3461
+ // Dim all content to make the mesh background more visible
3462
+ const mainContent = document.querySelector("pds-demo");
3463
+ if (mainContent && !this._originalOpacity) {
3464
+ this._originalOpacity = mainContent.style.opacity;
3465
+ mainContent.style.transition = "opacity 200ms ease-out";
3466
+ mainContent.style.opacity = "0.1";
3467
+ }
3468
+ }
3469
+
3470
+ clearMeshPreview() {
3471
+ if (this._originalBodyBg !== undefined) {
3472
+ if (this._originalBodyBg) {
3473
+ document.body.style.background = this._originalBodyBg;
3474
+ } else {
3475
+ document.body.style.removeProperty("background");
3476
+ document.body.style.removeProperty("background-attachment");
3477
+ }
3478
+ this._originalBodyBg = undefined;
3479
+ }
3480
+
3481
+ // Restore content opacity
3482
+ const mainContent = document.querySelector("pds-demo");
3483
+ if (mainContent && this._originalOpacity !== undefined) {
3484
+ mainContent.style.opacity = this._originalOpacity || "1";
3485
+ this._originalOpacity = undefined;
3486
+ // Remove transition after animation completes
3487
+ setTimeout(() => {
3488
+ if (mainContent.style.opacity !== "0.1") {
3489
+ mainContent.style.transition = "";
3490
+ }
3491
+ }, 200);
3492
+ }
3493
+ }
3494
+
3495
+ // Toast handler methods
3496
+ showSuccessToast() {
3497
+ toast("Your changes have been saved successfully!", {
3498
+ type: "success",
3499
+ });
3500
+ }
3501
+
3502
+ showInfoToast() {
3503
+ toast("This is an informational message with helpful context.", {
3504
+ type: "info",
3505
+ });
3506
+ }
3507
+
3508
+ showWarningToast() {
3509
+ toast("Warning: This action cannot be undone!", {
3510
+ type: "warning",
3511
+ });
3512
+ }
3513
+
3514
+ showErrorToast() {
3515
+ toast("Error: Something went wrong. Please try again.", {
3516
+ type: "error",
3517
+ });
3518
+ }
3519
+
3520
+ showLongToast() {
3521
+ toast(
3522
+ "This is a longer toast notification message that demonstrates how the duration is automatically calculated based on the message length. The toast will stay visible longer to give you enough time to read the entire message.",
3523
+ { type: "info" }
3524
+ );
3525
+ }
3526
+
3527
+ showPersistentToast() {
3528
+ toast(
3529
+ "This is a persistent toast that won't auto-dismiss. Click the × to close it.",
3530
+ {
3531
+ type: "info",
3532
+ persistent: true,
3533
+ }
3534
+ );
3535
+ }
3536
+
3537
+ triggerTransitionDemo() {
3538
+ const ball = this.querySelector("#transition-ball");
3539
+ if (!ball) return;
3540
+
3541
+ // Remove the animated class to reset
3542
+ ball.classList.remove("animated");
3543
+
3544
+ // Force a reflow to restart the animation
3545
+ void ball.offsetWidth;
3546
+
3547
+ // Add the animated class to trigger the transition
3548
+ ball.classList.add("animated");
3549
+
3550
+ // Reset after animation completes (using slow transition as max)
3551
+ setTimeout(() => {
3552
+ ball.classList.remove("animated");
3553
+ }, 1000);
3554
+ }
3555
+
3556
+ renderColorCard(name, color) {
3557
+ return html`
3558
+ <div class="color-card">
3559
+ <div
3560
+ class="color-card-header"
3561
+ style="background-color: var(--color-${color}-600);"
3562
+ >
3563
+ ${name}
3564
+ </div>
3565
+ <div class="color-card-body">
3566
+ <div class="color-scale-grid">
3567
+ ${[50, 100, 200, 300, 400, 500, 600, 700, 800].map(
3568
+ (shade) => html`
3569
+ <div
3570
+ class="color-scale-swatch"
3571
+ style="background-color: var(--color-${color}-${shade});"
3572
+ title="${color}-${shade}"
3573
+ ></div>
3574
+ `
3575
+ )}
3576
+ </div>
3577
+ <p class="color-card-footer">9-step scale from 50 to 800</p>
3578
+ </div>
3579
+ </div>
3580
+ `;
3581
+ }
3582
+
3583
+ renderColorScale(colorName) {
3584
+ return html`
3585
+ <div class="color-scale-container">
3586
+ <div class="color-scale-row">
3587
+ <div class="color-scale-label">${colorName}</div>
3588
+ <div class="color-scale-swatches">
3589
+ ${[50, 100, 200, 300, 400, 500, 600, 700, 800].map((shade) => {
3590
+ const textColor =
3591
+ shade >= 400 ? "white" : `var(--color-${colorName}-900)`;
3592
+ return html`
3593
+ <div
3594
+ class="color-scale-swatch-interactive"
3595
+ style="
3596
+ background: var(--color-${colorName}-${shade});
3597
+ color: ${textColor};
3598
+ "
3599
+ @mouseover="${(e) => {
3600
+ e.currentTarget.style.transform = "translateY(-4px)";
3601
+ e.currentTarget.style.zIndex = "10";
3602
+ e.currentTarget.style.boxShadow = "var(--shadow-md)";
3603
+ }}"
3604
+ @mouseout="${(e) => {
3605
+ e.currentTarget.style.transform = "translateY(0)";
3606
+ e.currentTarget.style.zIndex = "1";
3607
+ e.currentTarget.style.boxShadow = "none";
3608
+ }}"
3609
+ title="${colorName}-${shade}"
3610
+ >
3611
+ ${shade}
3612
+ </div>
3613
+ `;
3614
+ })}
3615
+ </div>
3616
+ </div>
3617
+ </div>
3618
+ `;
3619
+ }
3620
+ }
3621
+ );