diff --git a/public/admin/dashboard.html b/public/admin/dashboard.html deleted file mode 100644 index 1a7d0fa3..00000000 --- a/public/admin/dashboard.html +++ /dev/null @@ -1,218 +0,0 @@ - - - - - - Admin Dashboard | Tractatus Framework - - - - - - - - - - -
- - -
-

Dashboard Overview

- - -
-
-
-
- -
-
-

Total Documents

-

-

-
-
-
- -
-
-
- -
-
-

Pending Review

-

-

-
-
-
- -
-
-
- -
-
-

Published Posts

-

-

-
-
-
- -
-
-
- -
-
-

Total Users

-

-

-
-
-
-
- - -
-
-
-
- -
-
-

Database Sync Status

-
-

Checking...

- Unknown -
-

Loading sync health...

-
-
-
- -
-
-
- - -
-
-

Recent Activity

-
-
-
Loading activity...
-
-
-
- - - - - - - - - - -
- - - - - - - - diff --git a/public/admin/hooks-dashboard.html b/public/admin/hooks-dashboard.html deleted file mode 100644 index c1528680..00000000 --- a/public/admin/hooks-dashboard.html +++ /dev/null @@ -1,180 +0,0 @@ - - - - - - Framework Hooks Dashboard | Tractatus Admin - - - - - - - -
- - - -
- - -
-

Framework Enforcement Metrics

-

Real-time monitoring of Claude Code hook validators and architectural enforcement

-
- - -
- -
-
-
- -
-
-

Total Hook Executions

-

-

-
-
-
- - -
-
-
- -
-
-

Operations Blocked

-

-

-
-
-
- - -
-
-
- -
-
-

Block Rate

-

-

-
-
-
- - -
-
-
- -
-
-

Last Activity

-

-

-
-
-
-
- - -
- -
-
-

- - - - Edit Hook -

-
-
-
-
- Total Executions: - - -
-
- Blocks: - - -
-
- Success Rate: - - -
-
-
-
- - -
-
-

- - - - Write Hook -

-
-
-
-
- Total Executions: - - -
-
- Blocks: - - -
-
- Success Rate: - - -
-
-
-
-
- - -
-
-

Recent Blocked Operations

-
-
-
-
No blocked operations
-
-
-
- - -
-
-

Recent Hook Executions

- -
-
-
-
Loading activity...
-
-
-
- -
- - - - - diff --git a/public/admin/login.html b/public/admin/login.html deleted file mode 100644 index 99f27b4f..00000000 --- a/public/admin/login.html +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - Admin Login | Tractatus Framework - - - - - -
-
- - -
-
- -
-

- Admin Portal -

-

- Tractatus Framework Management -

-
- - -
-
-
- - -
-
- - -
-
- - - - - -
- -
- - -
-

- Enter your admin credentials to continue -

-
-
- - - - -
-
- - - - - diff --git a/public/admin/rule-manager.html b/public/admin/rule-manager.html deleted file mode 100644 index bea7355f..00000000 --- a/public/admin/rule-manager.html +++ /dev/null @@ -1,279 +0,0 @@ - - - - - - Rule Manager | Multi-Project Governance - - - - - - - - - - - -
- - -
-
-

Governance Rules

-

Manage multi-project governance rules and policies

-
- -
- - -
- - -
- -
-
-
- -
-
-

Total Rules

-

-

-
-
-
- - -
-
-
- -
-
-

Universal

-

-

-
-
-
- - -
-
-
- -
-
-

Validated

-

-

-
-
-
- - -
-
-
- -
-
-

Avg Clarity

-

-

-
-
-
-
- - -
-
-

Filters

-
-
-
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
-
- - -
- - -
- - -
- - - - -
-
-
- - -
-
-
-

Rules

-
- Sort: - -
-
-
- - -
-
-
-

Loading rules...

-
-
- - - -
- -
- - - - - -
- -
- - - - - - - diff --git a/public/css/fonts.css b/public/css/fonts.css deleted file mode 100644 index 69fe8c0e..00000000 --- a/public/css/fonts.css +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Self-hosted Inter font for optimal performance - * Downloaded from Google Fonts v20 - * Optimized WOFF2 format for best compression - */ - -/* Inter Regular (400) */ -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url('/fonts/inter-400.woff2') format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} - -/* Inter Medium (500) */ -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 500; - font-display: swap; - src: url('/fonts/inter-500.woff2') format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} - -/* Inter SemiBold (600) */ -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 600; - font-display: swap; - src: url('/fonts/inter-600.woff2') format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} - -/* Inter Bold (700) */ -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 700; - font-display: swap; - src: url('/fonts/inter-700.woff2') format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} - -/* Inter ExtraBold (800) */ -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 800; - font-display: swap; - src: url('/fonts/inter-800.woff2') format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} diff --git a/public/css/src/tailwind.css b/public/css/src/tailwind.css deleted file mode 100644 index b5c61c95..00000000 --- a/public/css/src/tailwind.css +++ /dev/null @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/public/css/tailwind.css b/public/css/tailwind.css deleted file mode 100644 index 43710463..00000000 --- a/public/css/tailwind.css +++ /dev/null @@ -1 +0,0 @@ -*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.18 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.\!container{width:100%!important}.container{width:100%}@media (min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media (min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media (min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media (min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media (min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-y-0{top:0;bottom:0}.-top-16{top:-4rem}.bottom-0{bottom:0}.left-0{left:0}.left-1\/2{left:50%}.right-0{right:0}.top-0{top:0}.top-24{top:6rem}.top-6{top:1.5rem}.top-8{top:2rem}.z-10{z-index:10}.z-50{z-index:50}.mx-auto{margin-left:auto;margin-right:auto}.my-4{margin-top:1rem;margin-bottom:1rem}.-mb-px{margin-bottom:-1px}.mb-1{margin-bottom:.25rem}.mb-12{margin-bottom:3rem}.mb-16{margin-bottom:4rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-10{margin-left:2.5rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.ml-7{margin-left:1.75rem}.mr-2{margin-right:.5rem}.mr-3{margin-right:.75rem}.mr-4{margin-right:1rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-12{margin-top:3rem}.mt-16{margin-top:4rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.line-clamp-3{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.contents{display:contents}.hidden{display:none}.h-10{height:2.5rem}.h-12{height:3rem}.h-16{height:4rem}.h-2{height:.5rem}.h-24{height:6rem}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-full{height:100%}.max-h-\[80vh\]{max-height:80vh}.max-h-\[90vh\]{max-height:90vh}.min-h-\[400px\]{min-height:400px}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-12{width:3rem}.w-16{width:4rem}.w-2{width:.5rem}.w-24{width:6rem}.w-4{width:1rem}.w-48{width:12rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-8{width:2rem}.w-full{width:100%}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-4xl{max-width:56rem}.max-w-5xl{max-width:64rem}.max-w-6xl{max-width:72rem}.max-w-7xl{max-width:80rem}.max-w-md{max-width:28rem}.max-w-none{max-width:none}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.-translate-x-1\/2{--tw-translate-x:-50%}.-translate-x-1\/2,.translate-x-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x:0px}.translate-x-full{--tw-translate-x:100%}.translate-x-full,.translate-y-1\/2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-1\/2{--tw-translate-y:50%}.rotate-45{--tw-rotate:45deg}.rotate-45,.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.resize{resize:both}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.25rem*var(--tw-space-x-reverse));margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.75rem*var(--tw-space-x-reverse));margin-left:calc(.75rem*(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-x-6>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1.5rem*var(--tw-space-x-reverse));margin-left:calc(1.5rem*(1 - var(--tw-space-x-reverse)))}.space-x-8>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(2rem*var(--tw-space-x-reverse));margin-left:calc(2rem*(1 - var(--tw-space-x-reverse)))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem*var(--tw-space-y-reverse))}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(2rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem*var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px*var(--tw-divide-y-reverse))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity,1))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.rounded-r-lg{border-top-right-radius:.5rem;border-bottom-right-radius:.5rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l-2{border-left-width:2px}.border-l-4{border-left-width:4px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-t-4{border-top-width:4px}.border-amber-400{--tw-border-opacity:1;border-color:rgb(251 191 36/var(--tw-border-opacity,1))}.border-amber-500{--tw-border-opacity:1;border-color:rgb(245 158 11/var(--tw-border-opacity,1))}.border-amber-600{--tw-border-opacity:1;border-color:rgb(217 119 6/var(--tw-border-opacity,1))}.border-blue-200{--tw-border-opacity:1;border-color:rgb(191 219 254/var(--tw-border-opacity,1))}.border-blue-300{--tw-border-opacity:1;border-color:rgb(147 197 253/var(--tw-border-opacity,1))}.border-blue-400{--tw-border-opacity:1;border-color:rgb(96 165 250/var(--tw-border-opacity,1))}.border-blue-500{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity,1))}.border-blue-600{--tw-border-opacity:1;border-color:rgb(37 99 235/var(--tw-border-opacity,1))}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity,1))}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity,1))}.border-gray-400{--tw-border-opacity:1;border-color:rgb(156 163 175/var(--tw-border-opacity,1))}.border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity,1))}.border-gray-800{--tw-border-opacity:1;border-color:rgb(31 41 55/var(--tw-border-opacity,1))}.border-green-200{--tw-border-opacity:1;border-color:rgb(187 247 208/var(--tw-border-opacity,1))}.border-green-300{--tw-border-opacity:1;border-color:rgb(134 239 172/var(--tw-border-opacity,1))}.border-green-400{--tw-border-opacity:1;border-color:rgb(74 222 128/var(--tw-border-opacity,1))}.border-green-500{--tw-border-opacity:1;border-color:rgb(34 197 94/var(--tw-border-opacity,1))}.border-green-600{--tw-border-opacity:1;border-color:rgb(22 163 74/var(--tw-border-opacity,1))}.border-indigo-500{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity,1))}.border-indigo-600{--tw-border-opacity:1;border-color:rgb(79 70 229/var(--tw-border-opacity,1))}.border-orange-500{--tw-border-opacity:1;border-color:rgb(249 115 22/var(--tw-border-opacity,1))}.border-orange-600{--tw-border-opacity:1;border-color:rgb(234 88 12/var(--tw-border-opacity,1))}.border-purple-400{--tw-border-opacity:1;border-color:rgb(192 132 252/var(--tw-border-opacity,1))}.border-purple-500{--tw-border-opacity:1;border-color:rgb(168 85 247/var(--tw-border-opacity,1))}.border-purple-600{--tw-border-opacity:1;border-color:rgb(147 51 234/var(--tw-border-opacity,1))}.border-red-200{--tw-border-opacity:1;border-color:rgb(254 202 202/var(--tw-border-opacity,1))}.border-red-300{--tw-border-opacity:1;border-color:rgb(252 165 165/var(--tw-border-opacity,1))}.border-red-500{--tw-border-opacity:1;border-color:rgb(239 68 68/var(--tw-border-opacity,1))}.border-teal-500{--tw-border-opacity:1;border-color:rgb(20 184 166/var(--tw-border-opacity,1))}.border-transparent{border-color:transparent}.border-white{--tw-border-opacity:1;border-color:rgb(255 255 255/var(--tw-border-opacity,1))}.border-yellow-200{--tw-border-opacity:1;border-color:rgb(254 240 138/var(--tw-border-opacity,1))}.border-yellow-500{--tw-border-opacity:1;border-color:rgb(234 179 8/var(--tw-border-opacity,1))}.bg-amber-100{--tw-bg-opacity:1;background-color:rgb(254 243 199/var(--tw-bg-opacity,1))}.bg-amber-50{--tw-bg-opacity:1;background-color:rgb(255 251 235/var(--tw-bg-opacity,1))}.bg-amber-600{--tw-bg-opacity:1;background-color:rgb(217 119 6/var(--tw-bg-opacity,1))}.bg-amber-800{--tw-bg-opacity:1;background-color:rgb(146 64 14/var(--tw-bg-opacity,1))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity,1))}.bg-blue-100{--tw-bg-opacity:1;background-color:rgb(219 234 254/var(--tw-bg-opacity,1))}.bg-blue-50{--tw-bg-opacity:1;background-color:rgb(239 246 255/var(--tw-bg-opacity,1))}.bg-blue-500{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.bg-blue-600{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity,1))}.bg-blue-700{--tw-bg-opacity:1;background-color:rgb(29 78 216/var(--tw-bg-opacity,1))}.bg-blue-800{--tw-bg-opacity:1;background-color:rgb(30 64 175/var(--tw-bg-opacity,1))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity,1))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity,1))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.bg-gray-500{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity,1))}.bg-gray-600{--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity,1))}.bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity,1))}.bg-gray-900\/60{background-color:rgba(17,24,39,.6)}.bg-green-100{--tw-bg-opacity:1;background-color:rgb(220 252 231/var(--tw-bg-opacity,1))}.bg-green-50{--tw-bg-opacity:1;background-color:rgb(240 253 244/var(--tw-bg-opacity,1))}.bg-green-500{--tw-bg-opacity:1;background-color:rgb(34 197 94/var(--tw-bg-opacity,1))}.bg-green-600{--tw-bg-opacity:1;background-color:rgb(22 163 74/var(--tw-bg-opacity,1))}.bg-green-700{--tw-bg-opacity:1;background-color:rgb(21 128 61/var(--tw-bg-opacity,1))}.bg-indigo-100{--tw-bg-opacity:1;background-color:rgb(224 231 255/var(--tw-bg-opacity,1))}.bg-indigo-50{--tw-bg-opacity:1;background-color:rgb(238 242 255/var(--tw-bg-opacity,1))}.bg-orange-100{--tw-bg-opacity:1;background-color:rgb(255 237 213/var(--tw-bg-opacity,1))}.bg-orange-50{--tw-bg-opacity:1;background-color:rgb(255 247 237/var(--tw-bg-opacity,1))}.bg-orange-500{--tw-bg-opacity:1;background-color:rgb(249 115 22/var(--tw-bg-opacity,1))}.bg-orange-600{--tw-bg-opacity:1;background-color:rgb(234 88 12/var(--tw-bg-opacity,1))}.bg-pink-100{--tw-bg-opacity:1;background-color:rgb(252 231 243/var(--tw-bg-opacity,1))}.bg-purple-100{--tw-bg-opacity:1;background-color:rgb(243 232 255/var(--tw-bg-opacity,1))}.bg-purple-50{--tw-bg-opacity:1;background-color:rgb(250 245 255/var(--tw-bg-opacity,1))}.bg-purple-500{--tw-bg-opacity:1;background-color:rgb(168 85 247/var(--tw-bg-opacity,1))}.bg-purple-600{--tw-bg-opacity:1;background-color:rgb(147 51 234/var(--tw-bg-opacity,1))}.bg-purple-700{--tw-bg-opacity:1;background-color:rgb(126 34 206/var(--tw-bg-opacity,1))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity,1))}.bg-red-200{--tw-bg-opacity:1;background-color:rgb(254 202 202/var(--tw-bg-opacity,1))}.bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.bg-red-500{--tw-bg-opacity:1;background-color:rgb(239 68 68/var(--tw-bg-opacity,1))}.bg-red-600{--tw-bg-opacity:1;background-color:rgb(220 38 38/var(--tw-bg-opacity,1))}.bg-red-700{--tw-bg-opacity:1;background-color:rgb(185 28 28/var(--tw-bg-opacity,1))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-yellow-100{--tw-bg-opacity:1;background-color:rgb(254 249 195/var(--tw-bg-opacity,1))}.bg-yellow-50{--tw-bg-opacity:1;background-color:rgb(254 252 232/var(--tw-bg-opacity,1))}.bg-yellow-500{--tw-bg-opacity:1;background-color:rgb(234 179 8/var(--tw-bg-opacity,1))}.bg-opacity-20{--tw-bg-opacity:0.2}.bg-opacity-50{--tw-bg-opacity:0.5}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-amber-50{--tw-gradient-from:#fffbeb var(--tw-gradient-from-position);--tw-gradient-to:rgba(255,251,235,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-amber-500{--tw-gradient-from:#f59e0b var(--tw-gradient-from-position);--tw-gradient-to:rgba(245,158,11,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-amber-600{--tw-gradient-from:#d97706 var(--tw-gradient-from-position);--tw-gradient-to:rgba(217,119,6,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-blue-50{--tw-gradient-from:#eff6ff var(--tw-gradient-from-position);--tw-gradient-to:rgba(239,246,255,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-blue-500{--tw-gradient-from:#3b82f6 var(--tw-gradient-from-position);--tw-gradient-to:rgba(59,130,246,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-blue-600{--tw-gradient-from:#2563eb var(--tw-gradient-from-position);--tw-gradient-to:rgba(37,99,235,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-green-50{--tw-gradient-from:#f0fdf4 var(--tw-gradient-from-position);--tw-gradient-to:rgba(240,253,244,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-green-600{--tw-gradient-from:#16a34a var(--tw-gradient-from-position);--tw-gradient-to:rgba(22,163,74,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-purple-50{--tw-gradient-from:#faf5ff var(--tw-gradient-from-position);--tw-gradient-to:rgba(250,245,255,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-purple-500{--tw-gradient-from:#a855f7 var(--tw-gradient-from-position);--tw-gradient-to:rgba(168,85,247,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-purple-600{--tw-gradient-from:#9333ea var(--tw-gradient-from-position);--tw-gradient-to:rgba(147,51,234,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.via-blue-700{--tw-gradient-to:rgba(29,78,216,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),#1d4ed8 var(--tw-gradient-via-position),var(--tw-gradient-to)}.via-orange-50{--tw-gradient-to:rgba(255,247,237,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),#fff7ed var(--tw-gradient-via-position),var(--tw-gradient-to)}.to-amber-100{--tw-gradient-to:#fef3c7 var(--tw-gradient-to-position)}.to-blue-50{--tw-gradient-to:#eff6ff var(--tw-gradient-to-position)}.to-blue-600{--tw-gradient-to:#2563eb var(--tw-gradient-to-position)}.to-indigo-50{--tw-gradient-to:#eef2ff var(--tw-gradient-to-position)}.to-indigo-600{--tw-gradient-to:#4f46e5 var(--tw-gradient-to-position)}.to-orange-600{--tw-gradient-to:#ea580c var(--tw-gradient-to-position)}.to-purple-50{--tw-gradient-to:#faf5ff var(--tw-gradient-to-position)}.to-purple-600{--tw-gradient-to:#9333ea var(--tw-gradient-to-position)}.to-purple-700{--tw-gradient-to:#7e22ce var(--tw-gradient-to-position)}.to-teal-50{--tw-gradient-to:#f0fdfa var(--tw-gradient-to-position)}.to-teal-600{--tw-gradient-to:#0d9488 var(--tw-gradient-to-position)}.p-12{padding:3rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-12{padding-left:3rem;padding-right:3rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-20{padding-top:5rem;padding-bottom:5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pl-2{padding-left:.5rem}.pl-3{padding-left:.75rem}.pl-4{padding-left:1rem}.pl-6{padding-left:1.5rem}.pr-10{padding-right:2.5rem}.pt-24{padding-top:6rem}.pt-4{padding-top:1rem}.pt-8{padding-top:2rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-5xl{font-size:3rem;line-height:1}.text-6xl{font-size:3.75rem;line-height:1}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-extrabold{font-weight:800}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.italic{font-style:italic}.leading-none{line-height:1}.tracking-wider{letter-spacing:.05em}.text-amber-100{--tw-text-opacity:1;color:rgb(254 243 199/var(--tw-text-opacity,1))}.text-amber-400{--tw-text-opacity:1;color:rgb(251 191 36/var(--tw-text-opacity,1))}.text-amber-600{--tw-text-opacity:1;color:rgb(217 119 6/var(--tw-text-opacity,1))}.text-amber-700{--tw-text-opacity:1;color:rgb(180 83 9/var(--tw-text-opacity,1))}.text-amber-800{--tw-text-opacity:1;color:rgb(146 64 14/var(--tw-text-opacity,1))}.text-amber-900{--tw-text-opacity:1;color:rgb(120 53 15/var(--tw-text-opacity,1))}.text-blue-100{--tw-text-opacity:1;color:rgb(219 234 254/var(--tw-text-opacity,1))}.text-blue-400{--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity,1))}.text-blue-500{--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity,1))}.text-blue-600{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity,1))}.text-blue-700{--tw-text-opacity:1;color:rgb(29 78 216/var(--tw-text-opacity,1))}.text-blue-800{--tw-text-opacity:1;color:rgb(30 64 175/var(--tw-text-opacity,1))}.text-blue-900{--tw-text-opacity:1;color:rgb(30 58 138/var(--tw-text-opacity,1))}.text-gray-100{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity,1))}.text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity,1))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity,1))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity,1))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity,1))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity,1))}.text-green-500{--tw-text-opacity:1;color:rgb(34 197 94/var(--tw-text-opacity,1))}.text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity,1))}.text-green-700{--tw-text-opacity:1;color:rgb(21 128 61/var(--tw-text-opacity,1))}.text-green-800{--tw-text-opacity:1;color:rgb(22 101 52/var(--tw-text-opacity,1))}.text-green-900{--tw-text-opacity:1;color:rgb(20 83 45/var(--tw-text-opacity,1))}.text-indigo-600{--tw-text-opacity:1;color:rgb(79 70 229/var(--tw-text-opacity,1))}.text-indigo-700{--tw-text-opacity:1;color:rgb(67 56 202/var(--tw-text-opacity,1))}.text-orange-600{--tw-text-opacity:1;color:rgb(234 88 12/var(--tw-text-opacity,1))}.text-orange-700{--tw-text-opacity:1;color:rgb(194 65 12/var(--tw-text-opacity,1))}.text-orange-800{--tw-text-opacity:1;color:rgb(154 52 18/var(--tw-text-opacity,1))}.text-pink-800{--tw-text-opacity:1;color:rgb(157 23 77/var(--tw-text-opacity,1))}.text-purple-100{--tw-text-opacity:1;color:rgb(243 232 255/var(--tw-text-opacity,1))}.text-purple-500{--tw-text-opacity:1;color:rgb(168 85 247/var(--tw-text-opacity,1))}.text-purple-600{--tw-text-opacity:1;color:rgb(147 51 234/var(--tw-text-opacity,1))}.text-purple-700{--tw-text-opacity:1;color:rgb(126 34 206/var(--tw-text-opacity,1))}.text-purple-800{--tw-text-opacity:1;color:rgb(107 33 168/var(--tw-text-opacity,1))}.text-purple-900{--tw-text-opacity:1;color:rgb(88 28 135/var(--tw-text-opacity,1))}.text-red-400{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity,1))}.text-red-600{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity,1))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity,1))}.text-red-800{--tw-text-opacity:1;color:rgb(153 27 27/var(--tw-text-opacity,1))}.text-red-900{--tw-text-opacity:1;color:rgb(127 29 29/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-yellow-600{--tw-text-opacity:1;color:rgb(202 138 4/var(--tw-text-opacity,1))}.text-yellow-700{--tw-text-opacity:1;color:rgb(161 98 7/var(--tw-text-opacity,1))}.text-yellow-800{--tw-text-opacity:1;color:rgb(133 77 14/var(--tw-text-opacity,1))}.text-yellow-900{--tw-text-opacity:1;color:rgb(113 63 18/var(--tw-text-opacity,1))}.underline{text-decoration-line:underline}.placeholder-gray-500::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(107 114 128/var(--tw-placeholder-opacity,1))}.placeholder-gray-500::placeholder{--tw-placeholder-opacity:1;color:rgb(107 114 128/var(--tw-placeholder-opacity,1))}.opacity-0{opacity:0}.opacity-25{opacity:.25}.opacity-75{opacity:.75}.opacity-90{opacity:.9}.opacity-95{opacity:.95}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-2xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px rgba(0,0,0,.25);--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-lg,.shadow-md{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.outline{outline-style:solid}.ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-blue-500{--tw-ring-opacity:1;--tw-ring-color:rgb(59 130 246/var(--tw-ring-opacity,1))}.blur{--tw-blur:blur(8px)}.blur,.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px)}.backdrop-blur-sm,.backdrop-filter{backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.duration-500{transition-duration:.5s}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.last\:border-0:last-child{border-width:0}.hover\:border-amber-600:hover{--tw-border-opacity:1;border-color:rgb(217 119 6/var(--tw-border-opacity,1))}.hover\:border-blue-500:hover{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity,1))}.hover\:border-blue-600:hover{--tw-border-opacity:1;border-color:rgb(37 99 235/var(--tw-border-opacity,1))}.hover\:border-gray-300:hover{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity,1))}.hover\:border-gray-600:hover{--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity,1))}.hover\:border-green-600:hover{--tw-border-opacity:1;border-color:rgb(22 163 74/var(--tw-border-opacity,1))}.hover\:border-purple-600:hover{--tw-border-opacity:1;border-color:rgb(147 51 234/var(--tw-border-opacity,1))}.hover\:bg-amber-50:hover{--tw-bg-opacity:1;background-color:rgb(255 251 235/var(--tw-bg-opacity,1))}.hover\:bg-amber-700:hover{--tw-bg-opacity:1;background-color:rgb(180 83 9/var(--tw-bg-opacity,1))}.hover\:bg-amber-900:hover{--tw-bg-opacity:1;background-color:rgb(120 53 15/var(--tw-bg-opacity,1))}.hover\:bg-blue-50:hover{--tw-bg-opacity:1;background-color:rgb(239 246 255/var(--tw-bg-opacity,1))}.hover\:bg-blue-700:hover{--tw-bg-opacity:1;background-color:rgb(29 78 216/var(--tw-bg-opacity,1))}.hover\:bg-blue-800:hover{--tw-bg-opacity:1;background-color:rgb(30 64 175/var(--tw-bg-opacity,1))}.hover\:bg-blue-900:hover{--tw-bg-opacity:1;background-color:rgb(30 58 138/var(--tw-bg-opacity,1))}.hover\:bg-gray-100:hover{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity,1))}.hover\:bg-gray-300:hover{--tw-bg-opacity:1;background-color:rgb(209 213 219/var(--tw-bg-opacity,1))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.hover\:bg-gray-700:hover{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity,1))}.hover\:bg-green-50:hover{--tw-bg-opacity:1;background-color:rgb(240 253 244/var(--tw-bg-opacity,1))}.hover\:bg-green-700:hover{--tw-bg-opacity:1;background-color:rgb(21 128 61/var(--tw-bg-opacity,1))}.hover\:bg-green-800:hover{--tw-bg-opacity:1;background-color:rgb(22 101 52/var(--tw-bg-opacity,1))}.hover\:bg-purple-50:hover{--tw-bg-opacity:1;background-color:rgb(250 245 255/var(--tw-bg-opacity,1))}.hover\:bg-purple-700:hover{--tw-bg-opacity:1;background-color:rgb(126 34 206/var(--tw-bg-opacity,1))}.hover\:bg-purple-800:hover{--tw-bg-opacity:1;background-color:rgb(107 33 168/var(--tw-bg-opacity,1))}.hover\:bg-red-700:hover{--tw-bg-opacity:1;background-color:rgb(185 28 28/var(--tw-bg-opacity,1))}.hover\:bg-white:hover{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.hover\:text-amber-300:hover{--tw-text-opacity:1;color:rgb(252 211 77/var(--tw-text-opacity,1))}.hover\:text-amber-700:hover{--tw-text-opacity:1;color:rgb(180 83 9/var(--tw-text-opacity,1))}.hover\:text-amber-800:hover{--tw-text-opacity:1;color:rgb(146 64 14/var(--tw-text-opacity,1))}.hover\:text-blue-300:hover{--tw-text-opacity:1;color:rgb(147 197 253/var(--tw-text-opacity,1))}.hover\:text-blue-500:hover{--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity,1))}.hover\:text-blue-600:hover{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity,1))}.hover\:text-blue-700:hover{--tw-text-opacity:1;color:rgb(29 78 216/var(--tw-text-opacity,1))}.hover\:text-blue-800:hover{--tw-text-opacity:1;color:rgb(30 64 175/var(--tw-text-opacity,1))}.hover\:text-blue-900:hover{--tw-text-opacity:1;color:rgb(30 58 138/var(--tw-text-opacity,1))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity,1))}.hover\:text-gray-700:hover{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.hover\:text-gray-900:hover{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity,1))}.hover\:text-green-700:hover{--tw-text-opacity:1;color:rgb(21 128 61/var(--tw-text-opacity,1))}.hover\:text-purple-700:hover{--tw-text-opacity:1;color:rgb(126 34 206/var(--tw-text-opacity,1))}.hover\:text-red-900:hover{--tw-text-opacity:1;color:rgb(127 29 29/var(--tw-text-opacity,1))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-80:hover{opacity:.8}.hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.hover\:shadow-lg:hover,.hover\:shadow-md:hover{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.hover\:shadow-xl:hover{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.hover\:shadow-amber-100:hover{--tw-shadow-color:#fef3c7;--tw-shadow:var(--tw-shadow-colored)}.hover\:shadow-blue-100:hover{--tw-shadow-color:#dbeafe;--tw-shadow:var(--tw-shadow-colored)}.hover\:shadow-gray-100:hover{--tw-shadow-color:#f3f4f6;--tw-shadow:var(--tw-shadow-colored)}.hover\:shadow-green-100:hover{--tw-shadow-color:#dcfce7;--tw-shadow:var(--tw-shadow-colored)}.hover\:shadow-purple-100:hover{--tw-shadow-color:#f3e8ff;--tw-shadow:var(--tw-shadow-colored)}.focus\:z-10:focus{z-index:10}.focus\:border-blue-500:focus{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity,1))}.focus\:border-blue-600:focus{--tw-border-opacity:1;border-color:rgb(37 99 235/var(--tw-border-opacity,1))}.focus\:border-transparent:focus{border-color:transparent}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-blue-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(59 130 246/var(--tw-ring-opacity,1))}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:text-blue-400{--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity,1))}.group:hover .group-hover\:text-purple-600{--tw-text-opacity:1;color:rgb(147 51 234/var(--tw-text-opacity,1))}.group:hover .group-hover\:opacity-100{opacity:1}@media (min-width:640px){.sm\:inline{display:inline}.sm\:hidden{display:none}.sm\:flex-row{flex-direction:row}.sm\:space-x-6>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1.5rem*var(--tw-space-x-reverse));margin-left:calc(1.5rem*(1 - var(--tw-space-x-reverse)))}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:text-base{font-size:1rem;line-height:1.5rem}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}@media (min-width:768px){.md\:ml-4{margin-left:1rem}.md\:block{display:block}.md\:flex{display:flex}.md\:w-64{width:16rem}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:p-12{padding:3rem}.md\:text-2xl{font-size:1.5rem;line-height:2rem}.md\:text-5xl{font-size:3rem;line-height:1}.md\:text-6xl{font-size:3.75rem;line-height:1}}@media (min-width:1024px){.lg\:col-span-1{grid-column:span 1/span 1}.lg\:col-span-2{grid-column:span 2/span 2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:px-8{padding-left:2rem;padding-right:2rem}} \ No newline at end of file diff --git a/public/css/tractatus-theme.css b/public/css/tractatus-theme.css deleted file mode 100644 index 18b66b7d..00000000 --- a/public/css/tractatus-theme.css +++ /dev/null @@ -1,1008 +0,0 @@ -/** - * Tractatus AI Safety Framework - Theme System - * - * Based on TRACTATUS_BRAND_SYSTEM.md - * Created: 2025-10-18 - * - * This file defines the complete color, typography, and design token system - * for the Tractatus Framework. It implements the 6-node hexagonal orbital - * brand identity with service-specific colors. - */ - -:root { - /* ======================================== - * CORE IDENTITY - Cyan to Blue Gradient - * Shared with MySovereignty Passport - * ======================================== */ - --tractatus-core-start: #64ffda; /* Cyan 300 - Clarity, transparency */ - --tractatus-core-mid: #448aff; /* Blue 400 - Trust, logic */ - --tractatus-core-end: #0891b2; /* Cyan 600 - Technical precision (WCAG AA: 4.57:1 on gray-50) */ - - /* ======================================== - * SIX GOVERNANCE SERVICES - * Hexagonal node colors mapped to framework components - * ======================================== */ - - /* 1. BoundaryEnforcer - Emerald Green */ - --service-boundary-light: #10b981; /* Emerald 500 */ - --service-boundary-dark: #059669; /* Emerald 600 */ - - /* 2. InstructionPersistenceClassifier - Indigo */ - --service-instruction-light: #6366f1; /* Indigo 500 */ - --service-instruction-dark: #4f46e5; /* Indigo 600 */ - - /* 3. CrossReferenceValidator - Purple */ - --service-validator-light: #8b5cf6; /* Purple 500 */ - --service-validator-dark: #7c3aed; /* Purple 600 */ - - /* 4. ContextPressureMonitor - Amber */ - --service-pressure-light: #f59e0b; /* Amber 500 */ - --service-pressure-dark: #d97706; /* Amber 600 */ - - /* 5. MetacognitiveVerifier - Rose */ - --service-metacognitive-light: #ec4899; /* Pink 500 */ - --service-metacognitive-dark: #db2777; /* Pink 600 */ - - /* 6. PluralisticDeliberationOrchestrator - Teal */ - --service-deliberation-light: #14b8a6; /* Teal 500 */ - --service-deliberation-dark: #0f766e; /* Teal 700 */ - - /* ======================================== - * UI NEUTRALS - Slate-based - * Technical, professional feel - * ======================================== */ - - /* Light mode backgrounds */ - --bg-primary: #ffffff; - --bg-secondary: #f8fafc; /* Slate 50 */ - --bg-tertiary: #f1f5f9; /* Slate 100 */ - - /* Text colors */ - --text-primary: #0f172a; /* Slate 900 */ - --text-secondary: #475569; /* Slate 600 */ - --text-tertiary: #94a3b8; /* Slate 400 */ - - /* Borders */ - --border-light: #e2e8f0; /* Slate 200 */ - --border-medium: #cbd5e1; /* Slate 300 */ - --border-dark: #94a3b8; /* Slate 400 */ - - /* Dark mode (future implementation) */ - --bg-primary-dark: #0f172a; /* Slate 900 */ - --bg-secondary-dark: #1e293b; /* Slate 800 */ - --text-primary-dark: #f8fafc; /* Slate 50 */ - - /* ======================================== - * SEMANTIC COLORS - * State and feedback colors - * ======================================== */ - - /* Success (safety achieved) */ - --success: #10b981; /* Emerald 500 - matches BoundaryEnforcer */ - --success-light: #d1fae5; /* Emerald 100 */ - --success-dark: #065f46; /* Emerald 800 */ - - /* Warning (attention needed) */ - --warning: #f59e0b; /* Amber 500 - matches PressureMonitor */ - --warning-light: #fef3c7; /* Amber 100 */ - --warning-dark: #92400e; /* Amber 800 */ - - /* Error (boundary violation) */ - --error: #ef4444; /* Red 500 */ - --error-light: #fee2e2; /* Red 100 */ - --error-dark: #991b1b; /* Red 800 */ - - /* Info (neutral notification) */ - --info: #0ea5e9; /* Cyan 500 - matches core */ - --info-light: #e0f2fe; /* Cyan 100 */ - --info-dark: #075985; /* Cyan 800 */ - - /* ======================================== - * TYPOGRAPHY SYSTEM - * Font families and weights - * ======================================== */ - - /* Font stacks */ - --font-display: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; - --font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - --font-mono: 'SF Mono', Monaco, 'Cascadia Code', monospace; - - /* Font weights */ - --font-light: 300; - --font-normal: 400; - --font-medium: 500; - --font-semibold: 600; - --font-bold: 700; - --font-extrabold: 800; - - /* ======================================== - * GRADIENTS - * Pre-defined gradient combinations - * ======================================== */ - - /* Primary hero gradient */ - --gradient-hero: linear-gradient(135deg, #64ffda 0%, #448aff 50%, #0ea5e9 100%); - - /* Primary CTA button */ - --gradient-primary-btn: linear-gradient(135deg, #0ea5e9 0%, #0284c7 100%); - - /* Service-specific button gradients */ - --gradient-btn-boundary: linear-gradient(135deg, #10b981 0%, #059669 100%); - --gradient-btn-instruction: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%); - --gradient-btn-validator: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%); - --gradient-btn-pressure: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); - --gradient-btn-metacognitive: linear-gradient(135deg, #ec4899 0%, #db2777 100%); - --gradient-btn-deliberation: linear-gradient(135deg, #14b8a6 0%, #0d9488 100%); - - /* Multi-service highlight gradient */ - --gradient-all-services: linear-gradient(90deg, - #10b981 0%, /* BoundaryEnforcer */ - #6366f1 20%, /* InstructionPersistence */ - #8b5cf6 40%, /* Validator */ - #f59e0b 60%, /* PressureMonitor */ - #ec4899 80%, /* Metacognitive */ - #14b8a6 100% /* Deliberation */ - ); - - /* ======================================== - * SPACING SCALE - * Consistent spacing throughout - * ======================================== */ - --spacing-xs: 0.25rem; /* 4px */ - --spacing-sm: 0.5rem; /* 8px */ - --spacing-md: 1rem; /* 16px */ - --spacing-lg: 1.5rem; /* 24px */ - --spacing-xl: 2rem; /* 32px */ - --spacing-2xl: 3rem; /* 48px */ - --spacing-3xl: 4rem; /* 64px */ - - /* ======================================== - * BORDER RADIUS - * Rounded corner styles - * ======================================== */ - --radius-sm: 0.25rem; /* 4px */ - --radius-md: 0.5rem; /* 8px */ - --radius-lg: 0.75rem; /* 12px */ - --radius-xl: 1rem; /* 16px */ - --radius-2xl: 1.5rem; /* 24px */ - --radius-full: 9999px; /* Fully rounded */ - - /* ======================================== - * SHADOWS - * Elevation system - * ======================================== */ - --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); - --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); - --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); - --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); - --shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25); - - /* ======================================== - * TRANSITIONS - * Standard animation timing - * ======================================== */ - --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1); - --transition-normal: 300ms cubic-bezier(0.4, 0, 0.2, 1); - --transition-slow: 500ms cubic-bezier(0.4, 0, 0.2, 1); - - /* ======================================== - * Z-INDEX SCALE - * Layer management - * ======================================== */ - --z-base: 0; - --z-dropdown: 1000; - --z-sticky: 1020; - --z-fixed: 1030; - --z-modal-backdrop: 1040; - --z-modal: 1050; - --z-popover: 1060; - --z-tooltip: 1070; -} - -/* ======================================== - * UTILITY CLASSES - * Common reusable patterns - * ======================================== */ - -/* Service color accent classes */ -.accent-boundary { border-left-color: var(--service-boundary-light); } -.accent-instruction { border-left-color: var(--service-instruction-light); } -.accent-validator { border-left-color: var(--service-validator-light); } -.accent-pressure { border-left-color: var(--service-pressure-light); } -.accent-metacognitive { border-left-color: var(--service-metacognitive-light); } -.accent-deliberation { border-left-color: var(--service-deliberation-light); } - -/* Text color utilities */ -.text-boundary { color: var(--service-boundary-light); } -.text-instruction { color: var(--service-instruction-light); } -.text-validator { color: var(--service-validator-light); } -.text-pressure { color: var(--service-pressure-light); } -.text-metacognitive { color: var(--service-metacognitive-light); } -.text-deliberation { color: var(--service-deliberation-light); } - -/* Background utilities */ -.bg-boundary { background-color: var(--service-boundary-light); } -.bg-instruction { background-color: var(--service-instruction-light); } -.bg-validator { background-color: var(--service-validator-light); } -.bg-pressure { background-color: var(--service-pressure-light); } -.bg-metacognitive { background-color: var(--service-metacognitive-light); } -.bg-deliberation { background-color: var(--service-deliberation-light); } - -/* Gradient backgrounds */ -.bg-gradient-hero { background: var(--gradient-hero); } -.bg-gradient-all-services { background: var(--gradient-all-services); } - -/* ======================================== - * COMPONENT BASE STYLES - * Foundational component patterns - * ======================================== */ - -/* Button base */ -.btn-base { - font-weight: var(--font-semibold); - padding: 0.75rem 2rem; - border-radius: var(--radius-md); - transition: all var(--transition-normal); - box-shadow: var(--shadow-md); -} - -.btn-base:hover { - box-shadow: var(--shadow-lg); - transform: translateY(-2px); -} - -/* Primary button */ -.btn-primary { - background: var(--gradient-primary-btn); - color: white; -} - -/* Service-specific buttons */ -.btn-boundary { background: var(--gradient-btn-boundary); color: white; } -.btn-instruction { background: var(--gradient-btn-instruction); color: white; } -.btn-validator { background: var(--gradient-btn-validator); color: white; } -.btn-pressure { background: var(--gradient-btn-pressure); color: white; } -.btn-metacognitive { background: var(--gradient-btn-metacognitive); color: white; } -.btn-deliberation { background: var(--gradient-btn-deliberation); color: white; } - -/* Card base */ -.card-base { - background: var(--bg-primary); - border-radius: var(--radius-xl); - box-shadow: var(--shadow-md); - padding: 2rem; - transition: all var(--transition-normal); -} - -.card-interactive:hover { - box-shadow: var(--shadow-xl); - transform: translateY(-4px); -} - -/* Service-specific cards (with left border accent) */ -.card-service { - border-left: 4px solid transparent; -} - -.card-service.boundary { border-left-color: var(--service-boundary-light); } -.card-service.instruction { border-left-color: var(--service-instruction-light); } -.card-service.validator { border-left-color: var(--service-validator-light); } -.card-service.pressure { border-left-color: var(--service-pressure-light); } -.card-service.metacognitive { border-left-color: var(--service-metacognitive-light); } -.card-service.deliberation { border-left-color: var(--service-deliberation-light); } - -/* ======================================== - * RESPONSIVE TYPOGRAPHY - * Fluid type scale - * ======================================== */ -.text-display-sm { font-size: clamp(2.5rem, 5vw, 3.5rem); font-family: var(--font-display); } -.text-display-md { font-size: clamp(3rem, 6vw, 4.5rem); font-family: var(--font-display); } -.text-display-lg { font-size: clamp(3.5rem, 8vw, 6rem); font-family: var(--font-display); } - -/* Apply Inter to headings */ -h1, h2, h3, h4, h5, h6 { - font-family: var(--font-display); - font-weight: 700; -} - -/* Fine-tune letter spacing for Inter */ -h1 { letter-spacing: -0.025em; } -h2 { letter-spacing: -0.02em; } -h3 { letter-spacing: -0.015em; } - -/* ======================================== - * ANIMATIONS & MICRO-INTERACTIONS - * Scroll effects and hover states - * ======================================== */ - -/* Keyframes */ -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes fadeInScale { - from { - opacity: 0; - transform: scale(0.95); - } - to { - opacity: 1; - transform: scale(1); - } -} - -@keyframes slideInLeft { - from { - opacity: 0; - transform: translateX(-30px); - } - to { - opacity: 1; - transform: translateX(0); - } -} - -@keyframes slideInRight { - from { - opacity: 0; - transform: translateX(30px); - } - to { - opacity: 1; - transform: translateX(0); - } -} - -@keyframes pulse { - 0%, 100% { - opacity: 1; - } - 50% { - opacity: 0.7; - } -} - -/* Animation utility classes */ -.animate-fade-in { - animation: fadeIn 0.6s ease-out; -} - -.animate-fade-in-scale { - animation: fadeInScale 0.5s ease-out; -} - -.animate-slide-in-left { - animation: slideInLeft 0.6s ease-out; -} - -.animate-slide-in-right { - animation: slideInRight 0.6s ease-out; -} - -.animate-pulse { - animation: pulse 2s ease-in-out infinite; -} - -/* Staggered animation delays */ -.animate-delay-100 { animation-delay: 100ms; } -.animate-delay-200 { animation-delay: 200ms; } -.animate-delay-300 { animation-delay: 300ms; } -.animate-delay-400 { animation-delay: 400ms; } -.animate-delay-500 { animation-delay: 500ms; } - -/* Hover effects */ -.hover-lift { - transition: transform var(--transition-normal), box-shadow var(--transition-normal); -} - -.hover-lift:hover { - transform: translateY(-4px); -} - -.hover-scale { - transition: transform var(--transition-normal); -} - -.hover-scale:hover { - transform: scale(1.05); -} - -.hover-glow { - transition: box-shadow var(--transition-normal); -} - -.hover-glow:hover { - box-shadow: 0 0 20px rgba(14, 165, 233, 0.3); -} - -/* ======================================== - * ACCESSIBILITY - * Focus states and reduced motion - * ======================================== */ - -/* Enhanced focus indicators */ -*:focus-visible { - outline: 3px solid var(--tractatus-core-end); - outline-offset: 2px; -} - -/* Respect user's motion preferences */ -@media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - } -} - -/* ======================================== - * LOADING STATES - * Spinner and loading indicators - * ======================================== */ - -/* Loading spinner */ -.spinner { - width: 40px; - height: 40px; - border: 4px solid var(--border-light); - border-top-color: var(--tractatus-core-end); - border-radius: 50%; - animation: spin 0.8s linear infinite; -} - -.spinner-sm { - width: 20px; - height: 20px; - border-width: 2px; -} - -.spinner-lg { - width: 60px; - height: 60px; - border-width: 6px; -} - -@keyframes spin { - to { transform: rotate(360deg); } -} - -/* Loading overlay */ -.loading-overlay { - position: absolute; - inset: 0; - background: rgba(255, 255, 255, 0.9); - backdrop-filter: blur(2px); - display: flex; - align-items: center; - justify-center; - z-index: 50; -} - -.loading-overlay-dark { - background: rgba(15, 23, 42, 0.9); -} - -/* Skeleton loading */ -.skeleton { - background: linear-gradient( - 90deg, - var(--bg-secondary) 0%, - var(--bg-tertiary) 50%, - var(--bg-secondary) 100% - ); - background-size: 200% 100%; - animation: skeleton-loading 1.5s ease-in-out infinite; - border-radius: 0.375rem; -} - -@keyframes skeleton-loading { - 0% { background-position: 200% 0; } - 100% { background-position: -200% 0; } -} - -.skeleton-text { - height: 1rem; - margin-bottom: 0.5rem; -} - -.skeleton-heading { - height: 2rem; - width: 60%; - margin-bottom: 1rem; -} - -/* Loading dots */ -.loading-dots { - display: inline-flex; - gap: 0.5rem; -} - -.loading-dots span { - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--tractatus-core-end); - animation: loading-dots 1.4s ease-in-out infinite; -} - -.loading-dots span:nth-child(2) { - animation-delay: 0.2s; -} - -.loading-dots span:nth-child(3) { - animation-delay: 0.4s; -} - -@keyframes loading-dots { - 0%, 80%, 100% { - opacity: 0.3; - transform: scale(0.8); - } - 40% { - opacity: 1; - transform: scale(1); - } -} - -/* ======================================== - * CSP-COMPLIANT UTILITY CLASSES - * Common styles extracted from inline attributes - * ======================================== */ - -/* Text Colors */ -.text-tractatus-link { - color: var(--tractatus-core-end); -} - -.text-service-boundary { - color: var(--service-boundary-light); -} - -.text-service-instruction { - color: var(--service-instruction-light); -} - -.text-service-validator { - color: var(--service-validator-light); -} - -.text-service-pressure { - color: var(--service-pressure-light); -} - -.text-service-metacognitive { - color: var(--service-metacognitive-light); -} - -.text-service-deliberation { - color: var(--service-deliberation-light); -} - -/* Border Colors */ -.border-l-tractatus { - border-left-color: var(--tractatus-core-end); -} - -.border-l-service-boundary { - border-left-color: var(--service-boundary-light); -} - -.border-l-service-instruction { - border-left-color: var(--service-instruction-light); -} - -.border-l-service-validator { - border-left-color: var(--service-validator-light); -} - -.border-l-service-pressure { - border-left-color: var(--service-pressure-light); -} - -.border-l-service-metacognitive { - border-left-color: var(--service-metacognitive-light); -} - -.border-l-service-deliberation { - border-left-color: var(--service-deliberation-light); -} - -/* Gradient Backgrounds */ -.bg-gradient-tractatus { - background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 50%, #7e22ce 100%); -} - -.bg-gradient-service-boundary { - background: linear-gradient(135deg, #10b981 0%, #059669 100%); -} - -.bg-gradient-service-instruction { - background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%); -} - -.bg-gradient-service-validator { - background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%); -} - -.bg-gradient-service-pressure { - background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); -} - -.bg-gradient-service-metacognitive { - background: linear-gradient(135deg, #ec4899 0%, #db2777 100%); -} - -.bg-gradient-service-deliberation { - background: linear-gradient(135deg, #14b8a6 0%, #0d9488 100%); -} - -.bg-gradient-cyan-blue { - background: linear-gradient(135deg, #0ea5e9 0%, #0284c7 100%); -} - -/* Text Shadows */ -.text-shadow-sm { - text-shadow: 0 1px 2px rgba(0,0,0,0.1); -} - -.text-shadow-md { - text-shadow: 0 2px 4px rgba(0,0,0,0.1); -} - -/* Badge Backgrounds (with matching text colors) */ -.badge-boundary { - color: #065f46; - background-color: #d1fae5; -} - -.badge-instruction { - color: #3730a3; - background-color: #e0e7ff; -} - -.badge-validator { - color: #581c87; - background-color: #f3e8ff; -} - -.badge-pressure { - color: #92400e; - background-color: #fef3c7; -} - -.badge-metacognitive { - color: #831843; - background-color: #fce7f3; -} - -.badge-deliberation { - color: #134e4a; - background-color: #ccfbf1; -} - -/* Layout Utilities */ -.min-h-16 { - min-height: 64px; -} - -/* Auth Error Page */ -.auth-error-container { - display: flex; - align-items: center; - justify-content: center; - height: 100vh; - font-family: system-ui, -apple-system, sans-serif; -} - -.auth-error-content { - text-align: center; -} - -.auth-error-icon { - width: 64px; - height: 64px; - margin: 0 auto 16px; - color: #3B82F6; -} - -.auth-error-title { - font-size: 20px; - font-weight: 600; - color: #111827; - margin-bottom: 8px; -} - -.auth-error-message { - color: #6B7280; - margin-bottom: 16px; -} - -.auth-error-redirect { - color: #9CA3AF; - font-size: 14px; -} - -/* Coming Soon Overlay */ -.coming-soon-overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.95); - z-index: 9999; - display: flex; - align-items: center; - justify-content: center; - backdrop-filter: blur(10px); -} - -.coming-soon-card { - background: white; - padding: 3rem; - border-radius: 1rem; - max-width: 600px; - text-align: center; - box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); -} - -.coming-soon-title { - font-size: 2.5rem; - font-weight: bold; - color: #1f2937; - margin-bottom: 1rem; -} - -.coming-soon-subtitle { - font-size: 1.25rem; - color: #6b7280; - margin-bottom: 2rem; -} - -.coming-soon-info-box { - background: #eff6ff; - border-left: 4px solid #3b82f6; - padding: 1.5rem; - margin-bottom: 2rem; - text-align: left; -} - -.coming-soon-info-title { - color: #1e40af; - margin-bottom: 0.5rem; -} - -.coming-soon-info-text { - color: #1e3a8a; - font-size: 0.875rem; - margin: 0; -} - -.coming-soon-status { - color: #6b7280; - font-size: 0.875rem; - margin-bottom: 1.5rem; -} - -.coming-soon-button { - display: inline-block; - background: #3b82f6; - color: white; - padding: 0.75rem 2rem; - border-radius: 0.5rem; - text-decoration: none; - font-weight: 600; - transition: background 0.2s; -} - -.coming-soon-button:hover { - background: #2563eb; -} - -.coming-soon-footer { - margin-top: 1.5rem; - font-size: 0.75rem; - color: #9ca3af; -} - -.coming-soon-footer a { - color: #3b82f6; - text-decoration: none; -} - -.coming-soon-footer a:hover { - text-decoration: underline; -} - -/* ======================================== - * SCROLL ANIMATIONS - * Intersection Observer-based scroll animations - * ======================================== */ - -/* Scroll animation initial states (before visible) */ -.animate-on-scroll { - transition: opacity 0.6s cubic-bezier(0.4, 0, 0.2, 1), - transform 0.6s cubic-bezier(0.4, 0, 0.2, 1); -} - -/* Fade in animation */ -.animate-on-scroll[data-animation="fade-in"] { - opacity: 0; -} - -.animate-on-scroll[data-animation="fade-in"].is-visible { - opacity: 1; -} - -/* Slide up animation */ -.animate-on-scroll[data-animation="slide-up"] { - opacity: 0; - transform: translateY(2rem); -} - -.animate-on-scroll[data-animation="slide-up"].is-visible { - opacity: 1; - transform: translateY(0); -} - -/* Slide down animation */ -.animate-on-scroll[data-animation="slide-down"] { - opacity: 0; - transform: translateY(-2rem); -} - -.animate-on-scroll[data-animation="slide-down"].is-visible { - opacity: 1; - transform: translateY(0); -} - -/* Slide left animation */ -.animate-on-scroll[data-animation="slide-left"] { - opacity: 0; - transform: translateX(2rem); -} - -.animate-on-scroll[data-animation="slide-left"].is-visible { - opacity: 1; - transform: translateX(0); -} - -/* Slide right animation */ -.animate-on-scroll[data-animation="slide-right"] { - opacity: 0; - transform: translateX(-2rem); -} - -.animate-on-scroll[data-animation="slide-right"].is-visible { - opacity: 1; - transform: translateX(0); -} - -/* Scale in animation */ -.animate-on-scroll[data-animation="scale-in"] { - opacity: 0; - transform: scale(0.95); -} - -.animate-on-scroll[data-animation="scale-in"].is-visible { - opacity: 1; - transform: scale(1); -} - -/* Rotate in animation */ -.animate-on-scroll[data-animation="rotate-in"] { - opacity: 0; - transform: rotate(12deg) scale(0.95); -} - -.animate-on-scroll[data-animation="rotate-in"].is-visible { - opacity: 1; - transform: rotate(0deg) scale(1); -} - -/* Default animation if no data-animation specified */ -.animate-on-scroll:not([data-animation]) { - opacity: 0; - transform: translateY(2rem); -} - -.animate-on-scroll:not([data-animation]).is-visible { - opacity: 1; - transform: translateY(0); -} - -/* Respect user's motion preferences for scroll animations */ -@media (prefers-reduced-motion: reduce) { - .animate-on-scroll { - opacity: 1 !important; - transform: none !important; - transition: none !important; - } -} - -/* ======================================== - * PAGE TRANSITIONS - * Smooth fade transitions between pages - * ======================================== */ -body { - transition: opacity 0.3s ease-in-out; -} - -body.page-fade-in { - opacity: 1; -} - -body.page-fade-out { - opacity: 0; -} - -/* Respect user's motion preferences for page transitions */ -@media (prefers-reduced-motion: reduce) { - body { - transition: none !important; - } -} - -/* ======================================== - * DATA VISUALIZATIONS - * Pressure chart and timeline components - * ======================================== */ -.gauge-fill-path { - transition: stroke 0.3s ease; -} - -.timeline-event { - transition: all 0.3s ease; -} - -.timeline-event:hover { - transform: scale(1.05); -} - -@media (prefers-reduced-motion: reduce) { - .gauge-fill-path, - .timeline-event { - transition: none !important; - } - - .timeline-event:hover { - transform: none !important; - } -} - -/* ======================================== - * DARK MODE SUPPORT (Future) - * Placeholder for dark mode implementation - * ======================================== */ -@media (prefers-color-scheme: dark) { - /* Will be implemented in Phase 3 */ -} - -/* ======================================== - * PRINT STYLES - * Optimize for PDF/print output - * ======================================== */ -@media print { - * { - background: white !important; - color: black !important; - box-shadow: none !important; - } - - a { - text-decoration: underline; - } - - .no-print { - display: none !important; - } -} diff --git a/public/css/tractatus-theme.min.css b/public/css/tractatus-theme.min.css deleted file mode 100644 index 26978f9b..00000000 --- a/public/css/tractatus-theme.min.css +++ /dev/null @@ -1,779 +0,0 @@ -/** - * Tractatus AI Safety Framework - Theme System - * - * Based on TRACTATUS_BRAND_SYSTEM.md - * Created: 2025-10-18 - * - * This file defines the complete color, typography, and design token system - * for the Tractatus Framework. It implements the 6-node hexagonal orbital - * brand identity with service-specific colors. - */ -:root { - /* ======================================== - * CORE IDENTITY - Cyan to Blue Gradient - * Shared with MySovereignty Passport - * ======================================== */ - --tractatus-core-start: #64ffda; - --tractatus-core-mid: #448aff; - --tractatus-core-end: #0891b2; - /* ======================================== - * SIX GOVERNANCE SERVICES - * Hexagonal node colors mapped to framework components - * ======================================== */ - --service-boundary-light: #10b981; - --service-boundary-dark: #059669; - --service-instruction-light: #6366f1; - --service-instruction-dark: #4f46e5; - --service-validator-light: #8b5cf6; - --service-validator-dark: #7c3aed; - --service-pressure-light: #f59e0b; - --service-pressure-dark: #d97706; - --service-metacognitive-light: #ec4899; - --service-metacognitive-dark: #db2777; - --service-deliberation-light: #14b8a6; - --service-deliberation-dark: #0f766e; - /* ======================================== - * UI NEUTRALS - Slate-based - * Technical, professional feel - * ======================================== */ - --bg-primary: #ffffff; - --bg-secondary: #f8fafc; - --bg-tertiary: #f1f5f9; - --text-primary: #0f172a; - --text-secondary: #475569; - --text-tertiary: #94a3b8; - --border-light: #e2e8f0; - --border-medium: #cbd5e1; - --border-dark: #94a3b8; - --bg-primary-dark: #0f172a; - --bg-secondary-dark: #1e293b; - --text-primary-dark: #f8fafc; - /* ======================================== - * SEMANTIC COLORS - * State and feedback colors - * ======================================== */ - --success: #10b981; - --success-light: #d1fae5; - --success-dark: #065f46; - --warning: #f59e0b; - --warning-light: #fef3c7; - --warning-dark: #92400e; - --error: #ef4444; - --error-light: #fee2e2; - --error-dark: #991b1b; - --info: #0ea5e9; - --info-light: #e0f2fe; - --info-dark: #075985; - /* ======================================== - * TYPOGRAPHY SYSTEM - * Font families and weights - * ======================================== */ - --font-display: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; - --font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - --font-mono: 'SF Mono', Monaco, 'Cascadia Code', monospace; - --font-light: 300; - --font-normal: 400; - --font-medium: 500; - --font-semibold: 600; - --font-bold: 700; - --font-extrabold: 800; - /* ======================================== - * GRADIENTS - * Pre-defined gradient combinations - * ======================================== */ - --gradient-hero: linear-gradient(135deg, #64ffda 0%, #448aff 50%, #0ea5e9 100%); - --gradient-primary-btn: linear-gradient(135deg, #0ea5e9 0%, #0284c7 100%); - --gradient-btn-boundary: linear-gradient(135deg, #10b981 0%, #059669 100%); - --gradient-btn-instruction: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%); - --gradient-btn-validator: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%); - --gradient-btn-pressure: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); - --gradient-btn-metacognitive: linear-gradient(135deg, #ec4899 0%, #db2777 100%); - --gradient-btn-deliberation: linear-gradient(135deg, #14b8a6 0%, #0d9488 100%); - --gradient-all-services: linear-gradient(90deg, - #10b981 0%, - #6366f1 20%, - #8b5cf6 40%, - #f59e0b 60%, - #ec4899 80%, - #14b8a6 100% - ); - /* ======================================== - * SPACING SCALE - * Consistent spacing throughout - * ======================================== */ - --spacing-xs: 0.25rem; - --spacing-sm: 0.5rem; - --spacing-md: 1rem; - --spacing-lg: 1.5rem; - --spacing-xl: 2rem; - --spacing-2xl: 3rem; - --spacing-3xl: 4rem; - /* ======================================== - * BORDER RADIUS - * Rounded corner styles - * ======================================== */ - --radius-sm: 0.25rem; - --radius-md: 0.5rem; - --radius-lg: 0.75rem; - --radius-xl: 1rem; - --radius-2xl: 1.5rem; - --radius-full: 9999px; - /* ======================================== - * SHADOWS - * Elevation system - * ======================================== */ - --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); - --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); - --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); - --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); - --shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25); - /* ======================================== - * TRANSITIONS - * Standard animation timing - * ======================================== */ - --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1); - --transition-normal: 300ms cubic-bezier(0.4, 0, 0.2, 1); - --transition-slow: 500ms cubic-bezier(0.4, 0, 0.2, 1); - /* ======================================== - * Z-INDEX SCALE - * Layer management - * ======================================== */ - --z-base: 0; - --z-dropdown: 1000; - --z-sticky: 1020; - --z-fixed: 1030; - --z-modal-backdrop: 1040; - --z-modal: 1050; - --z-popover: 1060; - --z-tooltip: 1070; -} -/* ======================================== - * UTILITY CLASSES - * Common reusable patterns - * ======================================== */ -.accent-boundary { border-left-color: var(--service-boundary-light); } -.accent-instruction { border-left-color: var(--service-instruction-light); } -.accent-validator { border-left-color: var(--service-validator-light); } -.accent-pressure { border-left-color: var(--service-pressure-light); } -.accent-metacognitive { border-left-color: var(--service-metacognitive-light); } -.accent-deliberation { border-left-color: var(--service-deliberation-light); } -.text-boundary { color: var(--service-boundary-light); } -.text-instruction { color: var(--service-instruction-light); } -.text-validator { color: var(--service-validator-light); } -.text-pressure { color: var(--service-pressure-light); } -.text-metacognitive { color: var(--service-metacognitive-light); } -.text-deliberation { color: var(--service-deliberation-light); } -.bg-boundary { background-color: var(--service-boundary-light); } -.bg-instruction { background-color: var(--service-instruction-light); } -.bg-validator { background-color: var(--service-validator-light); } -.bg-pressure { background-color: var(--service-pressure-light); } -.bg-metacognitive { background-color: var(--service-metacognitive-light); } -.bg-deliberation { background-color: var(--service-deliberation-light); } -.bg-gradient-hero { background: var(--gradient-hero); } -.bg-gradient-all-services { background: var(--gradient-all-services); } -/* ======================================== - * COMPONENT BASE STYLES - * Foundational component patterns - * ======================================== */ -.btn-base { - font-weight: var(--font-semibold); - padding: 0.75rem 2rem; - border-radius: var(--radius-md); - transition: all var(--transition-normal); - box-shadow: var(--shadow-md); -} -.btn-base:hover { - box-shadow: var(--shadow-lg); - transform: translateY(-2px); -} -.btn-primary { - background: var(--gradient-primary-btn); - color: white; -} -.btn-boundary { background: var(--gradient-btn-boundary); color: white; } -.btn-instruction { background: var(--gradient-btn-instruction); color: white; } -.btn-validator { background: var(--gradient-btn-validator); color: white; } -.btn-pressure { background: var(--gradient-btn-pressure); color: white; } -.btn-metacognitive { background: var(--gradient-btn-metacognitive); color: white; } -.btn-deliberation { background: var(--gradient-btn-deliberation); color: white; } -.card-base { - background: var(--bg-primary); - border-radius: var(--radius-xl); - box-shadow: var(--shadow-md); - padding: 2rem; - transition: all var(--transition-normal); -} -.card-interactive:hover { - box-shadow: var(--shadow-xl); - transform: translateY(-4px); -} -.card-service { - border-left: 4px solid transparent; -} -.card-service.boundary { border-left-color: var(--service-boundary-light); } -.card-service.instruction { border-left-color: var(--service-instruction-light); } -.card-service.validator { border-left-color: var(--service-validator-light); } -.card-service.pressure { border-left-color: var(--service-pressure-light); } -.card-service.metacognitive { border-left-color: var(--service-metacognitive-light); } -.card-service.deliberation { border-left-color: var(--service-deliberation-light); } -/* ======================================== - * RESPONSIVE TYPOGRAPHY - * Fluid type scale - * ======================================== */ -.text-display-sm { font-size: clamp(2.5rem, 5vw, 3.5rem); font-family: var(--font-display); } -.text-display-md { font-size: clamp(3rem, 6vw, 4.5rem); font-family: var(--font-display); } -.text-display-lg { font-size: clamp(3.5rem, 8vw, 6rem); font-family: var(--font-display); } -h1, h2, h3, h4, h5, h6 { - font-family: var(--font-display); - font-weight: 700; -} -h1 { letter-spacing: -0.025em; } -h2 { letter-spacing: -0.02em; } -h3 { letter-spacing: -0.015em; } -/* ======================================== - * ANIMATIONS & MICRO-INTERACTIONS - * Scroll effects and hover states - * ======================================== */ -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} -@keyframes fadeInScale { - from { - opacity: 0; - transform: scale(0.95); - } - to { - opacity: 1; - transform: scale(1); - } -} -@keyframes slideInLeft { - from { - opacity: 0; - transform: translateX(-30px); - } - to { - opacity: 1; - transform: translateX(0); - } -} -@keyframes slideInRight { - from { - opacity: 0; - transform: translateX(30px); - } - to { - opacity: 1; - transform: translateX(0); - } -} -@keyframes pulse { - 0%, 100% { - opacity: 1; - } - 50% { - opacity: 0.7; - } -} -.animate-fade-in { - animation: fadeIn 0.6s ease-out; -} -.animate-fade-in-scale { - animation: fadeInScale 0.5s ease-out; -} -.animate-slide-in-left { - animation: slideInLeft 0.6s ease-out; -} -.animate-slide-in-right { - animation: slideInRight 0.6s ease-out; -} -.animate-pulse { - animation: pulse 2s ease-in-out infinite; -} -.animate-delay-100 { animation-delay: 100ms; } -.animate-delay-200 { animation-delay: 200ms; } -.animate-delay-300 { animation-delay: 300ms; } -.animate-delay-400 { animation-delay: 400ms; } -.animate-delay-500 { animation-delay: 500ms; } -.hover-lift { - transition: transform var(--transition-normal), box-shadow var(--transition-normal); -} -.hover-lift:hover { - transform: translateY(-4px); -} -.hover-scale { - transition: transform var(--transition-normal); -} -.hover-scale:hover { - transform: scale(1.05); -} -.hover-glow { - transition: box-shadow var(--transition-normal); -} -.hover-glow:hover { - box-shadow: 0 0 20px rgba(14, 165, 233, 0.3); -} -/* ======================================== - * ACCESSIBILITY - * Focus states and reduced motion - * ======================================== */ -*:focus-visible { - outline: 3px solid var(--tractatus-core-end); - outline-offset: 2px; -} -@media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - } -} -/* ======================================== - * LOADING STATES - * Spinner and loading indicators - * ======================================== */ -.spinner { - width: 40px; - height: 40px; - border: 4px solid var(--border-light); - border-top-color: var(--tractatus-core-end); - border-radius: 50%; - animation: spin 0.8s linear infinite; -} -.spinner-sm { - width: 20px; - height: 20px; - border-width: 2px; -} -.spinner-lg { - width: 60px; - height: 60px; - border-width: 6px; -} -@keyframes spin { - to { transform: rotate(360deg); } -} -.loading-overlay { - position: absolute; - inset: 0; - background: rgba(255, 255, 255, 0.9); - backdrop-filter: blur(2px); - display: flex; - align-items: center; - justify-center; - z-index: 50; -} -.loading-overlay-dark { - background: rgba(15, 23, 42, 0.9); -} -.skeleton { - background: linear-gradient( - 90deg, - var(--bg-secondary) 0%, - var(--bg-tertiary) 50%, - var(--bg-secondary) 100% - ); - background-size: 200% 100%; - animation: skeleton-loading 1.5s ease-in-out infinite; - border-radius: 0.375rem; -} -@keyframes skeleton-loading { - 0% { background-position: 200% 0; } - 100% { background-position: -200% 0; } -} -.skeleton-text { - height: 1rem; - margin-bottom: 0.5rem; -} -.skeleton-heading { - height: 2rem; - width: 60%; - margin-bottom: 1rem; -} -.loading-dots { - display: inline-flex; - gap: 0.5rem; -} -.loading-dots span { - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--tractatus-core-end); - animation: loading-dots 1.4s ease-in-out infinite; -} -.loading-dots span:nth-child(2) { - animation-delay: 0.2s; -} -.loading-dots span:nth-child(3) { - animation-delay: 0.4s; -} -@keyframes loading-dots { - 0%, 80%, 100% { - opacity: 0.3; - transform: scale(0.8); - } - 40% { - opacity: 1; - transform: scale(1); - } -} -/* ======================================== - * CSP-COMPLIANT UTILITY CLASSES - * Common styles extracted from inline attributes - * ======================================== */ -.text-tractatus-link { - color: var(--tractatus-core-end); -} -.text-service-boundary { - color: var(--service-boundary-light); -} -.text-service-instruction { - color: var(--service-instruction-light); -} -.text-service-validator { - color: var(--service-validator-light); -} -.text-service-pressure { - color: var(--service-pressure-light); -} -.text-service-metacognitive { - color: var(--service-metacognitive-light); -} -.text-service-deliberation { - color: var(--service-deliberation-light); -} -.border-l-tractatus { - border-left-color: var(--tractatus-core-end); -} -.border-l-service-boundary { - border-left-color: var(--service-boundary-light); -} -.border-l-service-instruction { - border-left-color: var(--service-instruction-light); -} -.border-l-service-validator { - border-left-color: var(--service-validator-light); -} -.border-l-service-pressure { - border-left-color: var(--service-pressure-light); -} -.border-l-service-metacognitive { - border-left-color: var(--service-metacognitive-light); -} -.border-l-service-deliberation { - border-left-color: var(--service-deliberation-light); -} -.bg-gradient-tractatus { - background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 50%, #7e22ce 100%); -} -.bg-gradient-service-boundary { - background: linear-gradient(135deg, #10b981 0%, #059669 100%); -} -.bg-gradient-service-instruction { - background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%); -} -.bg-gradient-service-validator { - background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%); -} -.bg-gradient-service-pressure { - background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); -} -.bg-gradient-service-metacognitive { - background: linear-gradient(135deg, #ec4899 0%, #db2777 100%); -} -.bg-gradient-service-deliberation { - background: linear-gradient(135deg, #14b8a6 0%, #0d9488 100%); -} -.bg-gradient-cyan-blue { - background: linear-gradient(135deg, #0ea5e9 0%, #0284c7 100%); -} -.text-shadow-sm { - text-shadow: 0 1px 2px rgba(0,0,0,0.1); -} -.text-shadow-md { - text-shadow: 0 2px 4px rgba(0,0,0,0.1); -} -.badge-boundary { - color: #065f46; - background-color: #d1fae5; -} -.badge-instruction { - color: #3730a3; - background-color: #e0e7ff; -} -.badge-validator { - color: #581c87; - background-color: #f3e8ff; -} -.badge-pressure { - color: #92400e; - background-color: #fef3c7; -} -.badge-metacognitive { - color: #831843; - background-color: #fce7f3; -} -.badge-deliberation { - color: #134e4a; - background-color: #ccfbf1; -} -.min-h-16 { - min-height: 64px; -} -.auth-error-container { - display: flex; - align-items: center; - justify-content: center; - height: 100vh; - font-family: system-ui, -apple-system, sans-serif; -} -.auth-error-content { - text-align: center; -} -.auth-error-icon { - width: 64px; - height: 64px; - margin: 0 auto 16px; - color: #3B82F6; -} -.auth-error-title { - font-size: 20px; - font-weight: 600; - color: #111827; - margin-bottom: 8px; -} -.auth-error-message { - color: #6B7280; - margin-bottom: 16px; -} -.auth-error-redirect { - color: #9CA3AF; - font-size: 14px; -} -.coming-soon-overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.95); - z-index: 9999; - display: flex; - align-items: center; - justify-content: center; - backdrop-filter: blur(10px); -} -.coming-soon-card { - background: white; - padding: 3rem; - border-radius: 1rem; - max-width: 600px; - text-align: center; - box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); -} -.coming-soon-title { - font-size: 2.5rem; - font-weight: bold; - color: #1f2937; - margin-bottom: 1rem; -} -.coming-soon-subtitle { - font-size: 1.25rem; - color: #6b7280; - margin-bottom: 2rem; -} -.coming-soon-info-box { - background: #eff6ff; - border-left: 4px solid #3b82f6; - padding: 1.5rem; - margin-bottom: 2rem; - text-align: left; -} -.coming-soon-info-title { - color: #1e40af; - margin-bottom: 0.5rem; -} -.coming-soon-info-text { - color: #1e3a8a; - font-size: 0.875rem; - margin: 0; -} -.coming-soon-status { - color: #6b7280; - font-size: 0.875rem; - margin-bottom: 1.5rem; -} -.coming-soon-button { - display: inline-block; - background: #3b82f6; - color: white; - padding: 0.75rem 2rem; - border-radius: 0.5rem; - text-decoration: none; - font-weight: 600; - transition: background 0.2s; -} -.coming-soon-button:hover { - background: #2563eb; -} -.coming-soon-footer { - margin-top: 1.5rem; - font-size: 0.75rem; - color: #9ca3af; -} -.coming-soon-footer a { - color: #3b82f6; - text-decoration: none; -} -.coming-soon-footer a:hover { - text-decoration: underline; -} -/* ======================================== - * SCROLL ANIMATIONS - * Intersection Observer-based scroll animations - * ======================================== */ -.animate-on-scroll { - transition: opacity 0.6s cubic-bezier(0.4, 0, 0.2, 1), - transform 0.6s cubic-bezier(0.4, 0, 0.2, 1); -} -.animate-on-scroll[data-animation="fade-in"] { - opacity: 0; -} -.animate-on-scroll[data-animation="fade-in"].is-visible { - opacity: 1; -} -.animate-on-scroll[data-animation="slide-up"] { - opacity: 0; - transform: translateY(2rem); -} -.animate-on-scroll[data-animation="slide-up"].is-visible { - opacity: 1; - transform: translateY(0); -} -.animate-on-scroll[data-animation="slide-down"] { - opacity: 0; - transform: translateY(-2rem); -} -.animate-on-scroll[data-animation="slide-down"].is-visible { - opacity: 1; - transform: translateY(0); -} -.animate-on-scroll[data-animation="slide-left"] { - opacity: 0; - transform: translateX(2rem); -} -.animate-on-scroll[data-animation="slide-left"].is-visible { - opacity: 1; - transform: translateX(0); -} -.animate-on-scroll[data-animation="slide-right"] { - opacity: 0; - transform: translateX(-2rem); -} -.animate-on-scroll[data-animation="slide-right"].is-visible { - opacity: 1; - transform: translateX(0); -} -.animate-on-scroll[data-animation="scale-in"] { - opacity: 0; - transform: scale(0.95); -} -.animate-on-scroll[data-animation="scale-in"].is-visible { - opacity: 1; - transform: scale(1); -} -.animate-on-scroll[data-animation="rotate-in"] { - opacity: 0; - transform: rotate(12deg) scale(0.95); -} -.animate-on-scroll[data-animation="rotate-in"].is-visible { - opacity: 1; - transform: rotate(0deg) scale(1); -} -.animate-on-scroll:not([data-animation]) { - opacity: 0; - transform: translateY(2rem); -} -.animate-on-scroll:not([data-animation]).is-visible { - opacity: 1; - transform: translateY(0); -} -@media (prefers-reduced-motion: reduce) { - .animate-on-scroll { - opacity: 1 !important; - transform: none !important; - transition: none !important; - } -} -/* ======================================== - * PAGE TRANSITIONS - * Smooth fade transitions between pages - * ======================================== */ -body { - transition: opacity 0.3s ease-in-out; -} -body.page-fade-in { - opacity: 1; -} -body.page-fade-out { - opacity: 0; -} -@media (prefers-reduced-motion: reduce) { - body { - transition: none !important; - } -} -/* ======================================== - * DATA VISUALIZATIONS - * Pressure chart and timeline components - * ======================================== */ -.gauge-fill-path { - transition: stroke 0.3s ease; -} -.timeline-event { - transition: all 0.3s ease; -} -.timeline-event:hover { - transform: scale(1.05); -} -@media (prefers-reduced-motion: reduce) { - .gauge-fill-path, - .timeline-event { - transition: none !important; - } - .timeline-event:hover { - transform: none !important; - } -} -/* ======================================== - * DARK MODE SUPPORT (Future) - * Placeholder for dark mode implementation - * ======================================== */ -@media (prefers-color-scheme: dark) { -} -/* ======================================== - * PRINT STYLES - * Optimize for PDF/print output - * ======================================== */ -@media print { - * { - background: white !important; - color: black !important; - box-shadow: none !important; - } - a { - text-decoration: underline; - } - .no-print { - display: none !important; - } -} diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index e806c482..00000000 --- a/public/favicon.ico +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/public/favicon.svg b/public/favicon.svg deleted file mode 100644 index e806c482..00000000 --- a/public/favicon.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/public/fonts/inter-400.woff2 b/public/fonts/inter-400.woff2 deleted file mode 100644 index d15208de..00000000 Binary files a/public/fonts/inter-400.woff2 and /dev/null differ diff --git a/public/fonts/inter-500.woff2 b/public/fonts/inter-500.woff2 deleted file mode 100644 index 024f0770..00000000 Binary files a/public/fonts/inter-500.woff2 and /dev/null differ diff --git a/public/fonts/inter-600.woff2 b/public/fonts/inter-600.woff2 deleted file mode 100644 index d7509147..00000000 Binary files a/public/fonts/inter-600.woff2 and /dev/null differ diff --git a/public/fonts/inter-700.woff2 b/public/fonts/inter-700.woff2 deleted file mode 100644 index a40c4699..00000000 Binary files a/public/fonts/inter-700.woff2 and /dev/null differ diff --git a/public/fonts/inter-800.woff2 b/public/fonts/inter-800.woff2 deleted file mode 100644 index 6e7141f8..00000000 Binary files a/public/fonts/inter-800.woff2 and /dev/null differ diff --git a/public/images/architecture-diagram-generic.svg b/public/images/architecture-diagram-generic.svg deleted file mode 100644 index 9d8edb27..00000000 --- a/public/images/architecture-diagram-generic.svg +++ /dev/null @@ -1 +0,0 @@ -

Human Approval Workflows

Persistent Storage Layer (Immutable Audit Trail)

Tractatus Governance Layer (External Enforcement)

Agent Runtime Layer (Any LLM Agent System)

All actions pass through governance checks

Boundary violation

Values conflict

Approval/Rejection

Agentic AI Runtime
LangChain • AutoGPT • CrewAI
Claude Code • Custom Agents
Multi-Agent Systems
Tool Use • Planning • Execution

BoundaryEnforcer
Blocks values decisions
• Privacy policies
• Ethical trade-offs
• Strategic direction
• User agency violations
⚠ Cannot be bypassed by prompting

InstructionPersistenceClassifier
Classifies & stores instructions
• Quadrant (STR/OPS/TAC/SYS)
• Persistence (HIGH/MED/LOW)
• Temporal scope
⚠ External to AI memory

CrossReferenceValidator
Prevents pattern bias override
• Checks instruction history
• Detects conflicts (27027)
• Blocks contradictions
⚠ Independent verification

ContextPressureMonitor
Detects degraded conditions
• Token budget tracking
• Error accumulation
• Checkpoint reporting
⚠ Objective metrics, not self-reported

MetacognitiveVerifier
Validates complex operations
• >3 files or >5 steps
• Architecture changes
• Confidence scoring
⚠ Structural pause-and-verify

PluralisticDeliberationOrchestrator
Facilitates values deliberation
• Multi-stakeholder engagement
• Moral framework mapping
• Precedent documentation
⚠ Human judgment required

governance_rules
• rule_id (STR-001...)
• quadrant
• persistence level
• enforced_by
• violation_action
• active status

audit_logs
• timestamp
• service (which enforcer)
• action (BLOCK/WARN)
• instruction
• rule_violated
• session_id

session_state
• session_id
• token_count
• message_count
• pressure_level
• last_checkpoint
• framework_active

instruction_history
• instruction_id
• content
• classification
• persistence
• created_at
• active status

Human Oversight
Values Decisions
Strategic Changes
Boundary Violations
Final authority on incommensurable values

🔒 KEY JAILBREAK DEFENSE
Governance layer operates OUTSIDE agent runtime
Cannot be overridden by adversarial prompts
Structural boundaries, not behavioral training
Immutable audit trail independent of AI

\ No newline at end of file diff --git a/public/images/architecture-diagram-interactive.svg b/public/images/architecture-diagram-interactive.svg deleted file mode 100644 index cd107e53..00000000 --- a/public/images/architecture-diagram-interactive.svg +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - B - BoundaryEnforcer - Click for details - - - - - - I - InstructionPersistenceClassifier - Click for details - - - - - - V - CrossReferenceValidator - Click for details - - - - - - P - ContextPressureMonitor - Click for details - - - - - - M - MetacognitiveVerifier - Click for details - - - - - - D - PluralisticDeliberationOrchestrator - Click for details - - - - - - - - T - Tractatus - Tractatus Core - Click to see how all services work together - - - - diff --git a/public/images/architecture-diagram.png b/public/images/architecture-diagram.png deleted file mode 100644 index ce62aaa4..00000000 Binary files a/public/images/architecture-diagram.png and /dev/null differ diff --git a/public/images/architecture-diagram.svg b/public/images/architecture-diagram.svg deleted file mode 100644 index ef899511..00000000 --- a/public/images/architecture-diagram.svg +++ /dev/null @@ -1 +0,0 @@ -

Claude Code Runtime Environment

MongoDB Persistence Layer

Tractatus Governance Layer

API & Web Interface Layer

API Endpoints
/api/demo/classify
/api/demo/boundary-check
/api/demo/pressure-check
/api/admin/* • /api/auth/*

Web Interface
Interactive Demos
Admin Dashboard
Documentation
Blog System

BoundaryEnforcer
Blocks values decisions
• Privacy decisions
• Ethical trade-offs
• User agency violations

InstructionPersistenceClassifier
Classifies & stores instructions
• Quadrant (STR/OPS/TAC/SYS)
• Persistence (HIGH/MED/LOW)
• Temporal scope

CrossReferenceValidator
Prevents pattern bias override
• Checks instruction history
• Detects conflicts (27027)
• Blocks contradictions

ContextPressureMonitor
Detects degraded conditions
• Token budget tracking
• Error accumulation
• Checkpoint reporting

MetacognitiveVerifier
Self-checks complex operations
• >3 files or >5 steps
• Architecture changes
• Confidence scoring

PluralisticDeliberationOrchestrator
Facilitates values deliberation
• Multi-stakeholder engagement
• Moral framework mapping
• Precedent documentation

governance_rules
• rule_id (STR-001...)
• quadrant
• persistence level
• enforced_by
• violation_action
• active status

audit_logs
• timestamp
• service (which enforcer)
• action (BLOCK/WARN)
• instruction
• rule_violated
• session_id

session_state
• session_id
• token_count
• message_count
• pressure_level
• last_checkpoint
• framework_active

instruction_history
• instruction_id
• content
• classification
• persistence
• created_at
• active status

Base LLM Environment
Session Management • Tool Access
File System Operations
.claude/instruction-history.json
.claude/session-state.json
.claude/token-checkpoints.json
Context Window (200k tokens)

\ No newline at end of file diff --git a/public/js/admin/auth-check.js b/public/js/admin/auth-check.js deleted file mode 100644 index 9d2e1e6b..00000000 --- a/public/js/admin/auth-check.js +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Admin Authentication Check Utility - * Protects admin pages by redirecting unauthenticated users to login - * - * Usage: Include at top of every admin page HTML: - * - */ - -(function() { - 'use strict'; - - // Skip auth check on login page itself - if (window.location.pathname === '/admin/login.html') { - return; - } - - /** - * Check if user has valid authentication token - */ - function checkAuthentication() { - const token = localStorage.getItem('admin_token'); - - // No token found - redirect to login - if (!token) { - redirectToLogin('No authentication token found'); - return false; - } - - // Parse token to check expiration - try { - const payload = parseJWT(token); - const now = Math.floor(Date.now() / 1000); - - // Token expired - redirect to login - if (payload.exp && payload.exp < now) { - localStorage.removeItem('admin_token'); - redirectToLogin('Session expired'); - return false; - } - - // Check if admin role - if (payload.role !== 'admin' && payload.role !== 'moderator') { - redirectToLogin('Insufficient permissions'); - return false; - } - - // Token valid - return true; - - } catch (error) { - console.error('Token validation error:', error); - localStorage.removeItem('admin_token'); - redirectToLogin('Invalid authentication token'); - return false; - } - } - - /** - * Parse JWT token without verification (client-side validation only) - */ - function parseJWT(token) { - const parts = token.split('.'); - if (parts.length !== 3) { - throw new Error('Invalid token format'); - } - - const payload = parts[1]; - const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/')); - return JSON.parse(decoded); - } - - /** - * Redirect to login page with reason - */ - function redirectToLogin(reason) { - const currentPath = encodeURIComponent(window.location.pathname + window.location.search); - const loginUrl = `/admin/login.html?redirect=${currentPath}&reason=${encodeURIComponent(reason)}`; - - // Show brief message before redirect - document.body.innerHTML = ` -
-
- - - -

Authentication Required

-

${reason}

-

Redirecting to login...

-
-
- `; - - setTimeout(() => { - window.location.href = loginUrl; - }, 1500); - } - - /** - * Add authentication headers to fetch requests - */ - function getAuthHeaders() { - const token = localStorage.getItem('admin_token'); - return { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - }; - } - - /** - * Handle API authentication errors - */ - function handleAuthError(response) { - if (response.status === 401 || response.status === 403) { - localStorage.removeItem('admin_token'); - redirectToLogin('Session expired or invalid'); - return true; - } - return false; - } - - // Run authentication check immediately - checkAuthentication(); - - // Export utilities for admin pages to use - window.AdminAuth = { - getAuthHeaders, - handleAuthError, - checkAuthentication, - redirectToLogin - }; - - // Periodically check token validity (every 5 minutes) - setInterval(checkAuthentication, 5 * 60 * 1000); - -})(); diff --git a/public/js/admin/dashboard.js b/public/js/admin/dashboard.js deleted file mode 100644 index 6e5ecbef..00000000 --- a/public/js/admin/dashboard.js +++ /dev/null @@ -1,793 +0,0 @@ -// Auth check -const token = localStorage.getItem('admin_token'); -const user = JSON.parse(localStorage.getItem('admin_user') || '{}'); - -if (!token) { - window.location.href = '/admin/login.html'; -} - -// Display admin name -document.getElementById('admin-name').textContent = user.email || 'Admin'; - -// Logout -document.getElementById('logout-btn').addEventListener('click', () => { - localStorage.removeItem('admin_token'); - localStorage.removeItem('admin_user'); - window.location.href = '/admin/login.html'; -}); - -// Navigation -const navLinks = document.querySelectorAll('.nav-link'); -const sections = { - 'overview': document.getElementById('overview-section'), - 'moderation': document.getElementById('moderation-section'), - 'users': document.getElementById('users-section'), - 'documents': document.getElementById('documents-section') -}; - -navLinks.forEach(link => { - link.addEventListener('click', (e) => { - const href = link.getAttribute('href'); - - // Only handle hash-based navigation (internal sections) - // Let full URLs navigate normally - if (!href || !href.startsWith('#')) { - return; // Allow default navigation - } - - e.preventDefault(); - const section = href.substring(1); - - // Update active link - navLinks.forEach(l => l.classList.remove('active', 'bg-blue-100', 'text-blue-700')); - link.classList.add('active', 'bg-blue-100', 'text-blue-700'); - - // Show section - Object.values(sections).forEach(s => s.classList.add('hidden')); - if (sections[section]) { - sections[section].classList.remove('hidden'); - loadSection(section); - } - }); -}); - -// API helper -async function apiRequest(endpoint, options = {}) { - const response = await fetch(endpoint, { - ...options, - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json', - ...options.headers - } - }); - - if (response.status === 401) { - localStorage.removeItem('admin_token'); - window.location.href = '/admin/login.html'; - return; - } - - return response.json(); -} - -// Load statistics -async function loadStatistics() { - try { - const response = await apiRequest('/api/admin/stats'); - - if (!response.success || !response.stats) { - console.error('Invalid stats response:', response); - return; - } - - const stats = response.stats; - - document.getElementById('stat-documents').textContent = stats.documents?.total || 0; - document.getElementById('stat-pending').textContent = stats.moderation?.total_pending || 0; - document.getElementById('stat-approved').textContent = stats.blog?.published || 0; - document.getElementById('stat-users').textContent = stats.users?.total || 0; - } catch (error) { - console.error('Failed to load statistics:', error); - } -} - -// Load sync health status -async function loadSyncHealth() { - const statusEl = document.getElementById('sync-status'); - const badgeEl = document.getElementById('sync-badge'); - const detailsEl = document.getElementById('sync-details'); - const iconContainerEl = document.getElementById('sync-icon-container'); - - try { - const response = await apiRequest('/api/admin/sync/health'); - - if (!response.success || !response.health) { - console.error('Invalid sync health response:', response); - statusEl.textContent = 'Error'; - badgeEl.textContent = 'Error'; - badgeEl.className = 'px-2 py-1 text-xs rounded-full bg-red-100 text-red-800'; - detailsEl.textContent = 'Failed to check sync health'; - iconContainerEl.className = 'flex-shrink-0 bg-red-100 rounded-md p-3'; - return; - } - - const health = response.health; - const counts = health.counts; - - // Update status text - statusEl.textContent = `File: ${counts.file} | DB: ${counts.database}`; - - // Update badge and icon based on severity - if (health.severity === 'success') { - badgeEl.textContent = '✓ Synced'; - badgeEl.className = 'px-2 py-1 text-xs rounded-full bg-green-100 text-green-800'; - iconContainerEl.className = 'flex-shrink-0 bg-green-100 rounded-md p-3'; - iconContainerEl.querySelector('svg').classList.remove('text-gray-600', 'text-yellow-600', 'text-red-600'); - iconContainerEl.querySelector('svg').classList.add('text-green-600'); - } else if (health.severity === 'warning') { - badgeEl.textContent = '⚠ Desync'; - badgeEl.className = 'px-2 py-1 text-xs rounded-full bg-yellow-100 text-yellow-800'; - iconContainerEl.className = 'flex-shrink-0 bg-yellow-100 rounded-md p-3'; - iconContainerEl.querySelector('svg').classList.remove('text-gray-600', 'text-green-600', 'text-red-600'); - iconContainerEl.querySelector('svg').classList.add('text-yellow-600'); - } else { - badgeEl.textContent = '✗ Critical'; - badgeEl.className = 'px-2 py-1 text-xs rounded-full bg-red-100 text-red-800'; - iconContainerEl.className = 'flex-shrink-0 bg-red-100 rounded-md p-3'; - iconContainerEl.querySelector('svg').classList.remove('text-gray-600', 'text-green-600', 'text-yellow-600'); - iconContainerEl.querySelector('svg').classList.add('text-red-600'); - } - - // Update details - if (counts.difference === 0) { - detailsEl.textContent = health.message; - } else { - const missing = health.details?.missingInDatabase?.length || 0; - const orphaned = health.details?.orphanedInDatabase?.length || 0; - detailsEl.textContent = `${health.message} (Missing: ${missing}, Orphaned: ${orphaned})`; - } - } catch (error) { - console.error('Failed to load sync health:', error); - statusEl.textContent = 'Error'; - badgeEl.textContent = 'Error'; - badgeEl.className = 'px-2 py-1 text-xs rounded-full bg-red-100 text-red-800'; - detailsEl.textContent = 'Failed to check sync health'; - iconContainerEl.className = 'flex-shrink-0 bg-red-100 rounded-md p-3'; - } -} - -// Trigger manual sync -async function triggerSync() { - const button = document.getElementById('sync-trigger-btn'); - const originalText = button.textContent; - - try { - // Disable button and show loading state - button.disabled = true; - button.textContent = 'Syncing...'; - - const response = await apiRequest('/api/admin/sync/trigger', { - method: 'POST' - }); - - if (response.success) { - // Show success message - button.textContent = '✓ Synced'; - button.classList.remove('bg-blue-600', 'hover:bg-blue-700'); - button.classList.add('bg-green-600'); - - // Reload health status and stats - await loadSyncHealth(); - await loadStatistics(); - - // Reset button after 2 seconds - setTimeout(() => { - button.textContent = originalText; - button.classList.remove('bg-green-600'); - button.classList.add('bg-blue-600', 'hover:bg-blue-700'); - button.disabled = false; - }, 2000); - } else { - throw new Error(response.message || 'Sync failed'); - } - } catch (error) { - console.error('Manual sync error:', error); - button.textContent = '✗ Failed'; - button.classList.remove('bg-blue-600', 'hover:bg-blue-700'); - button.classList.add('bg-red-600'); - - // Reset button after 2 seconds - setTimeout(() => { - button.textContent = originalText; - button.classList.remove('bg-red-600'); - button.classList.add('bg-blue-600', 'hover:bg-blue-700'); - button.disabled = false; - }, 2000); - } -} - -// Load recent activity -async function loadRecentActivity() { - const container = document.getElementById('recent-activity'); - - try { - const response = await apiRequest('/api/admin/activity'); - - if (!response.success || !response.activity || response.activity.length === 0) { - container.innerHTML = '
No recent activity
'; - return; - } - - container.innerHTML = response.activity.map(item => { - // Generate description from activity data - const action = item.action || 'reviewed'; - const itemType = item.item_type || 'item'; - const description = `${action.charAt(0).toUpperCase() + action.slice(1)} ${itemType}`; - - return ` -
-
-
- ${getActivityIcon(action)} -
-
-
-

${description}

-

${formatDate(item.timestamp)}

-
-
- `; - }).join(''); - } catch (error) { - console.error('Failed to load activity:', error); - container.innerHTML = '
Failed to load activity
'; - } -} - -// Load moderation queue -async function loadModerationQueue(filter = 'all') { - const container = document.getElementById('moderation-queue'); - - try { - const response = await apiRequest(`/api/admin/moderation?type=${filter}`); - - if (!response.success || !response.items || response.items.length === 0) { - container.innerHTML = '
No items pending review
'; - return; - } - - container.innerHTML = response.items.map(item => ` -
-
-
-
- - ${item.type} - - ${formatDate(item.submitted_at)} -
-

${item.title}

-

${truncate(item.content || item.description, 150)}

-
-
- - -
-
-
- `).join(''); - } catch (error) { - console.error('Failed to load moderation queue:', error); - container.innerHTML = '
Failed to load queue
'; - } -} - -// Load users -async function loadUsers() { - const container = document.getElementById('users-list'); - - try { - const response = await apiRequest('/api/admin/users'); - - if (!response.success || !response.users || response.users.length === 0) { - container.innerHTML = '
No users found
'; - return; - } - - container.innerHTML = response.users.map(user => ` -
-
-
- ${user.email.charAt(0).toUpperCase()} -
-
-

${user.email}

-

Role: ${user.role}

-
-
-
- - ${user.role} - - ${user._id !== user._id ? ` - - ` : ''} -
-
- `).join(''); - } catch (error) { - console.error('Failed to load users:', error); - container.innerHTML = '
Failed to load users
'; - } -} - -// Load documents -async function loadDocuments() { - const container = document.getElementById('documents-list'); - - try { - const response = await apiRequest('/api/documents'); - - if (!response.success || !response.documents || response.documents.length === 0) { - container.innerHTML = '
No documents found
'; - return; - } - - container.innerHTML = response.documents.map(doc => { - const visibilityBadge = getVisibilityBadge(doc.visibility || 'internal'); - const statusBadge = getStatusBadge(doc.workflow_status || 'draft'); - const canPublish = doc.visibility === 'internal' && doc.workflow_status !== 'published'; - const canUnpublish = doc.visibility === 'public' && doc.workflow_status === 'published'; - - return ` -
-
-

${doc.title}

-
-

${doc.quadrant || 'No quadrant'}

- ${statusBadge} - ${visibilityBadge} - ${doc.category ? `Category: ${doc.category}` : ''} -
-
-
- - View - - ${canPublish ? ` - - ` : ''} - ${canUnpublish ? ` - - ` : ''} - -
-
- `; - }).join(''); - } catch (error) { - console.error('Failed to load documents:', error); - container.innerHTML = '
Failed to load documents
'; - } -} - -// Load section data -function loadSection(section) { - switch (section) { - case 'overview': - loadStatistics(); - loadRecentActivity(); - break; - case 'moderation': - loadModerationQueue(); - break; - case 'users': - loadUsers(); - break; - case 'documents': - loadDocuments(); - break; - } -} - -// Approve item -async function approveItem(itemId) { - if (!confirm('Approve this item?')) return; - - try { - const response = await apiRequest(`/api/admin/moderation/${itemId}/approve`, { - method: 'POST' - }); - - if (response.success) { - loadModerationQueue(); - loadStatistics(); - } else { - alert('Failed to approve item'); - } - } catch (error) { - console.error('Approval error:', error); - alert('Failed to approve item'); - } -} - -// Reject item -async function rejectItem(itemId) { - if (!confirm('Reject this item?')) return; - - try { - const response = await apiRequest(`/api/admin/moderation/${itemId}/reject`, { - method: 'POST' - }); - - if (response.success) { - loadModerationQueue(); - loadStatistics(); - } else { - alert('Failed to reject item'); - } - } catch (error) { - console.error('Rejection error:', error); - alert('Failed to reject item'); - } -} - -// Delete user -async function deleteUser(userId) { - if (!confirm('Delete this user? This action cannot be undone.')) return; - - try { - const response = await apiRequest(`/api/admin/users/${userId}`, { - method: 'DELETE' - }); - - if (response.success) { - loadUsers(); - loadStatistics(); - } else { - alert(response.message || 'Failed to delete user'); - } - } catch (error) { - console.error('Delete error:', error); - alert('Failed to delete user'); - } -} - -// Delete document -async function deleteDocument(docId) { - if (!confirm('Delete this document? This action cannot be undone.')) return; - - try { - const response = await apiRequest(`/api/documents/${docId}`, { - method: 'DELETE' - }); - - if (response.success) { - loadDocuments(); - loadStatistics(); - } else { - alert('Failed to delete document'); - } - } catch (error) { - console.error('Delete error:', error); - alert('Failed to delete document'); - } -} - -// Open publish modal -async function openPublishModal(docId) { - try { - const response = await apiRequest(`/api/documents/${docId}`); - if (!response.success || !response.document) { - alert('Failed to load document'); - return; - } - - const doc = response.document; - const modalHTML = ` -
-
-

Publish Document

- -
-

Title: ${doc.title}

-

Current Status: ${doc.workflow_status || 'draft'}

-
- -
-
- - -
- -
- - -

Higher numbers appear first

-
- -
- - -
-
-
-
- `; - - document.body.insertAdjacentHTML('beforeend', modalHTML); - - // Store doc ID for later - document.getElementById('publish-form').dataset.docId = docId; - - // Handle form submission - document.getElementById('publish-form').addEventListener('submit', async (e) => { - e.preventDefault(); - const category = document.getElementById('publish-category').value; - const order = parseInt(document.getElementById('publish-order').value) || 0; - - await publishDocument(docId, category, order); - }); - } catch (error) { - console.error('Failed to open publish modal:', error); - alert('Failed to open publish modal'); - } -} - -// Publish document -async function publishDocument(docId, category, order) { - try { - const response = await apiRequest(`/api/documents/${docId}/publish`, { - method: 'POST', - body: JSON.stringify({ category, order }) - }); - - if (response.success) { - closePublishModal(); - loadDocuments(); - loadStatistics(); - alert('Document published successfully'); - } else { - alert(response.message || 'Failed to publish document'); - } - } catch (error) { - console.error('Publish error:', error); - alert('Failed to publish document'); - } -} - -// Close publish modal -function closePublishModal() { - document.getElementById('publish-modal')?.remove(); -} - -// Open unpublish modal -async function openUnpublishModal(docId) { - try { - const response = await apiRequest(`/api/documents/${docId}`); - if (!response.success || !response.document) { - alert('Failed to load document'); - return; - } - - const doc = response.document; - const modalHTML = ` -
-
-

Unpublish Document

- -
-

Title: ${doc.title}

-

Current Visibility: ${doc.visibility}

-

Category: ${doc.category || 'None'}

-
- -
-
- - -

This will be recorded in the audit trail

-
- -
- - -
-
-
-
- `; - - document.body.insertAdjacentHTML('beforeend', modalHTML); - - // Store doc ID for later - document.getElementById('unpublish-form').dataset.docId = docId; - - // Handle form submission - document.getElementById('unpublish-form').addEventListener('submit', async (e) => { - e.preventDefault(); - const reason = document.getElementById('unpublish-reason').value; - - await unpublishDocument(docId, reason); - }); - } catch (error) { - console.error('Failed to open unpublish modal:', error); - alert('Failed to open unpublish modal'); - } -} - -// Unpublish document -async function unpublishDocument(docId, reason) { - try { - const response = await apiRequest(`/api/documents/${docId}/unpublish`, { - method: 'POST', - body: JSON.stringify({ reason }) - }); - - if (response.success) { - closeUnpublishModal(); - loadDocuments(); - loadStatistics(); - alert('Document unpublished successfully'); - } else { - alert(response.message || 'Failed to unpublish document'); - } - } catch (error) { - console.error('Unpublish error:', error); - alert('Failed to unpublish document'); - } -} - -// Close unpublish modal -function closeUnpublishModal() { - document.getElementById('unpublish-modal')?.remove(); -} - -// Utility functions -function getVisibilityBadge(visibility) { - const badges = { - 'public': 'Public', - 'internal': 'Internal', - 'confidential': 'Confidential', - 'archived': 'Archived' - }; - return badges[visibility] || badges['internal']; -} - -function getStatusBadge(status) { - const badges = { - 'draft': 'Draft', - 'review': 'Review', - 'published': 'Published' - }; - return badges[status] || badges['draft']; -} - -function getActivityColor(type) { - const colors = { - 'create': 'bg-green-500', - 'update': 'bg-blue-500', - 'delete': 'bg-red-500', - 'approve': 'bg-purple-500' - }; - return colors[type] || 'bg-gray-500'; -} - -function getActivityIcon(type) { - const icons = { - 'create': '+', - 'update': '↻', - 'delete': '×', - 'approve': '✓' - }; - return icons[type] || '•'; -} - -function formatDate(dateString) { - if (!dateString) return 'Unknown'; - const date = new Date(dateString); - return date.toLocaleString('en-US', { - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' - }); -} - -function truncate(str, length) { - if (!str) return ''; - return str.length > length ? str.substring(0, length) + '...' : str; -} - -// Queue filter -document.getElementById('queue-filter')?.addEventListener('change', (e) => { - loadModerationQueue(e.target.value); -}); - -// Initialize -loadStatistics(); -loadRecentActivity(); -loadSyncHealth(); - -// Auto-refresh sync health every 60 seconds -setInterval(() => { - loadSyncHealth(); -}, 60000); - -// Event delegation for data-action buttons (CSP compliance) -document.addEventListener('click', (e) => { - const button = e.target.closest('[data-action]'); - if (!button) return; - - const action = button.dataset.action; - const arg0 = button.dataset.arg0; - - switch (action) { - case 'approveItem': - approveItem(arg0); - break; - case 'rejectItem': - rejectItem(arg0); - break; - case 'deleteUser': - deleteUser(arg0); - break; - case 'deleteDocument': - deleteDocument(arg0); - break; - case 'openPublishModal': - openPublishModal(arg0); - break; - case 'openUnpublishModal': - openUnpublishModal(arg0); - break; - case 'closePublishModal': - closePublishModal(); - break; - case 'closeUnpublishModal': - closeUnpublishModal(); - break; - case 'triggerSync': - triggerSync(); - break; - } -}); diff --git a/public/js/admin/hooks-dashboard.js b/public/js/admin/hooks-dashboard.js deleted file mode 100644 index 984778b8..00000000 --- a/public/js/admin/hooks-dashboard.js +++ /dev/null @@ -1,212 +0,0 @@ -/** - * Hooks Dashboard - * Real-time monitoring of framework enforcement hooks - */ - -const API_BASE = window.location.hostname === 'localhost' ? 'http://localhost:9000/api' : '/api'; - -// Load metrics on page load -document.addEventListener('DOMContentLoaded', () => { - loadMetrics(); - - // Auto-refresh every 30 seconds - setInterval(loadMetrics, 30000); - - // Manual refresh button - document.getElementById('refresh-btn')?.addEventListener('click', loadMetrics); -}); - -/** - * Load hook metrics - */ -async function loadMetrics() { - try { - const response = await fetch(`${API_BASE}/admin/hooks/metrics`, { - headers: { - 'Authorization': `Bearer ${localStorage.getItem('admin_token')}` - } - }); - - if (!response.ok) { - throw new Error('Failed to load metrics'); - } - - const data = await response.json(); - displayMetrics(data); - } catch (error) { - console.error('Error loading metrics:', error); - showError('Failed to load hook metrics'); - } -} - -/** - * Display metrics in UI - */ -function displayMetrics(data) { - const metrics = data.metrics || {}; - const executions = metrics.hook_executions || []; - const blocks = metrics.blocks || []; - const stats = metrics.session_stats || {}; - - // Calculate totals - const totalExecutions = executions.length; - const totalBlocks = blocks.length; - const blockRate = totalExecutions > 0 ? ((totalBlocks / totalExecutions) * 100).toFixed(1) + '%' : '0%'; - - // Update quick stats - document.getElementById('stat-total-executions').textContent = totalExecutions.toLocaleString(); - document.getElementById('stat-total-blocks').textContent = totalBlocks.toLocaleString(); - document.getElementById('stat-block-rate').textContent = blockRate; - - // Last updated - const lastUpdated = stats.last_updated ? formatRelativeTime(new Date(stats.last_updated)) : 'Never'; - document.getElementById('stat-last-updated').textContent = lastUpdated; - - // Hook breakdown - const editExecutions = executions.filter(e => e.hook === 'validate-file-edit').length; - const editBlocks = blocks.filter(b => b.hook === 'validate-file-edit').length; - const editSuccessRate = editExecutions > 0 ? (((editExecutions - editBlocks) / editExecutions) * 100).toFixed(1) + '%' : '100%'; - - document.getElementById('edit-executions').textContent = editExecutions.toLocaleString(); - document.getElementById('edit-blocks').textContent = editBlocks.toLocaleString(); - document.getElementById('edit-success-rate').textContent = editSuccessRate; - - const writeExecutions = executions.filter(e => e.hook === 'validate-file-write').length; - const writeBlocks = blocks.filter(b => b.hook === 'validate-file-write').length; - const writeSuccessRate = writeExecutions > 0 ? (((writeExecutions - writeBlocks) / writeExecutions) * 100).toFixed(1) + '%' : '100%'; - - document.getElementById('write-executions').textContent = writeExecutions.toLocaleString(); - document.getElementById('write-blocks').textContent = writeBlocks.toLocaleString(); - document.getElementById('write-success-rate').textContent = writeSuccessRate; - - // Recent blocks - displayRecentBlocks(blocks.slice(-10).reverse()); - - // Recent activity - displayRecentActivity(executions.slice(-20).reverse()); -} - -/** - * Display recent blocked operations - */ -function displayRecentBlocks(blocks) { - const container = document.getElementById('recent-blocks'); - - if (blocks.length === 0) { - container.innerHTML = '
No blocked operations
'; - return; - } - - const html = blocks.map(block => ` -
-
-
- - - -
-
-
- ${block.hook.replace('validate-file-', '')} - ${formatRelativeTime(new Date(block.timestamp))} -
-

${escapeHtml(block.file)}

-

${escapeHtml(block.reason)}

-
-
-
- `).join(''); - - container.innerHTML = html; -} - -/** - * Display recent hook executions - */ -function displayRecentActivity(executions) { - const container = document.getElementById('recent-activity'); - - if (executions.length === 0) { - container.innerHTML = '
No recent activity
'; - return; - } - - const html = executions.map(exec => ` -
-
-
- ${exec.result === 'passed' ? ` - - - - ` : ` - - - - `} -
-
-
- ${exec.hook.replace('validate-file-', '')} - ${formatRelativeTime(new Date(exec.timestamp))} -
-

${escapeHtml(exec.file)}

- ${exec.reason ? `

${escapeHtml(exec.reason)}

` : ''} -
-
-
- `).join(''); - - container.innerHTML = html; -} - -/** - * Format relative time - */ -function formatRelativeTime(date) { - const now = new Date(); - const diffMs = now - date; - const diffSec = Math.floor(diffMs / 1000); - const diffMin = Math.floor(diffSec / 60); - const diffHour = Math.floor(diffMin / 60); - const diffDay = Math.floor(diffHour / 24); - - if (diffSec < 60) { - return 'Just now'; - } else if (diffMin < 60) { - return `${diffMin}m ago`; - } else if (diffHour < 24) { - return `${diffHour}h ago`; - } else if (diffDay < 7) { - return `${diffDay}d ago`; - } else { - return date.toLocaleDateString(); - } -} - -/** - * Escape HTML to prevent XSS - */ -function escapeHtml(text) { - const div = document.createElement('div'); - div.textContent = text; - return div.innerHTML; -} - -/** - * Show error message - */ -function showError(message) { - const container = document.getElementById('recent-activity'); - container.innerHTML = ` -
-

${escapeHtml(message)}

- -
- `; - - // Add event listener to retry button - document.getElementById('retry-load-btn')?.addEventListener('click', loadMetrics); -} diff --git a/public/js/admin/login.js b/public/js/admin/login.js deleted file mode 100644 index d3a86f02..00000000 --- a/public/js/admin/login.js +++ /dev/null @@ -1,59 +0,0 @@ -const loginForm = document.getElementById('login-form'); -const errorMessage = document.getElementById('error-message'); -const errorText = document.getElementById('error-text'); -const loginBtn = document.getElementById('login-btn'); - -loginForm.addEventListener('submit', async (e) => { - e.preventDefault(); - - const email = document.getElementById('email').value; - const password = document.getElementById('password').value; - - // Hide previous errors - errorMessage.classList.add('hidden'); - - // Disable button - loginBtn.disabled = true; - loginBtn.innerHTML = 'Signing in...'; - - try { - const response = await fetch('/api/auth/login', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ email, password }), - }); - - const data = await response.json(); - - if (response.ok && data.success) { - // Store token - localStorage.setItem('admin_token', data.accessToken); - localStorage.setItem('admin_user', JSON.stringify(data.user)); - - // Redirect to dashboard - window.location.href = '/admin/dashboard.html'; - } else { - // Show error - showError(data.message || 'Invalid credentials'); - loginBtn.disabled = false; - loginBtn.innerHTML = 'Sign in'; - } - } catch (error) { - console.error('Login error:', error); - showError('Network error. Please try again.'); - loginBtn.disabled = false; - loginBtn.innerHTML = 'Sign in'; - } -}); - -function showError(message) { - errorText.textContent = message; - errorMessage.classList.remove('hidden'); -} - -// Auto-fill for development (optional) -if (window.location.hostname === 'localhost') { - document.getElementById('email').value = 'admin@agenticgovernance.digital'; -} diff --git a/public/js/admin/project-editor.js b/public/js/admin/project-editor.js deleted file mode 100644 index 2ecdb539..00000000 --- a/public/js/admin/project-editor.js +++ /dev/null @@ -1,783 +0,0 @@ -/** - * Project Editor Modal - * Handles creation, editing, viewing, and variable management for projects - * - * @class ProjectEditor - */ - -class ProjectEditor { - constructor() { - this.mode = 'create'; // 'create', 'edit', 'view', 'variables' - this.projectId = null; - this.originalProject = null; - this.variables = []; - } - - /** - * Open editor in create mode - */ - openCreate() { - this.mode = 'create'; - this.projectId = null; - this.originalProject = null; - this.render(); - this.attachEventListeners(); - } - - /** - * Open editor in edit mode - */ - async openEdit(projectId) { - this.mode = 'edit'; - this.projectId = projectId; - - try { - const response = await apiRequest(`/api/admin/projects/${projectId}`); - - if (!response.success || !response.project) { - throw new Error('Failed to load project'); - } - - this.originalProject = response.project; - this.variables = response.variables || []; - this.render(); - this.populateForm(response.project); - this.attachEventListeners(); - } catch (error) { - console.error('Failed to load project:', error); - showToast('Failed to load project for editing', 'error'); - } - } - - /** - * Open editor in view mode (read-only) - */ - async openView(projectId) { - this.mode = 'view'; - this.projectId = projectId; - - try { - const response = await apiRequest(`/api/admin/projects/${projectId}`); - - if (!response.success || !response.project) { - throw new Error('Failed to load project'); - } - - this.originalProject = response.project; - this.variables = response.variables || []; - this.renderViewMode(response.project); - } catch (error) { - console.error('Failed to load project:', error); - showToast('Failed to load project', 'error'); - } - } - - /** - * Open variables management mode - */ - async openVariables(projectId) { - this.mode = 'variables'; - this.projectId = projectId; - - try { - const [projectResponse, variablesResponse] = await Promise.all([ - apiRequest(`/api/admin/projects/${projectId}`), - apiRequest(`/api/admin/projects/${projectId}/variables`) - ]); - - if (!projectResponse.success || !projectResponse.project) { - throw new Error('Failed to load project'); - } - - this.originalProject = projectResponse.project; - this.variables = variablesResponse.variables || []; - this.renderVariablesMode(); - } catch (error) { - console.error('Failed to load project variables:', error); - showToast('Failed to load variables', 'error'); - } - } - - /** - * Render the editor modal - */ - render() { - const container = document.getElementById('modal-container'); - const title = this.mode === 'create' ? 'Create New Project' : 'Edit Project'; - - container.innerHTML = ` -
-
- -
-

${title}

- -
- - -
-
-
- -
- - -

Lowercase slug format (letters, numbers, hyphens only)

-
- - -
- - -
- - -
- - -
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- - -
- - -
- - -

(Inactive projects are hidden from rule rendering)

-
-
-
-
- - -
- - -
-
-
- `; - } - - /** - * Render view mode (read-only) - */ - renderViewMode(project) { - const container = document.getElementById('modal-container'); - - const techStack = project.techStack || {}; - const metadata = project.metadata || {}; - - container.innerHTML = ` -
-
- -
-
-

${escapeHtml(project.name)}

-

${escapeHtml(project.id)}

-
- -
- - -
-
- -
- ${project.active - ? 'Active' - : 'Inactive' - } -
- - - ${project.description ? ` -
-

Description

-

${escapeHtml(project.description)}

-
- ` : ''} - - - ${Object.keys(techStack).length > 0 ? ` -
-

Tech Stack

-
- ${techStack.framework ? `
Framework: ${escapeHtml(techStack.framework)}
` : ''} - ${techStack.database ? `
Database: ${escapeHtml(techStack.database)}
` : ''} - ${techStack.frontend ? `
Frontend: ${escapeHtml(techStack.frontend)}
` : ''} - ${techStack.css ? `
CSS: ${escapeHtml(techStack.css)}
` : ''} -
-
- ` : ''} - - - ${project.repositoryUrl ? ` -
-

Repository

- - ${escapeHtml(project.repositoryUrl)} - -
- ` : ''} - - -
-
-

Variables (${this.variables.length})

- -
- ${this.variables.length > 0 ? ` -
- - - - - - - - - - ${this.variables.slice(0, 5).map(v => ` - - - - - - `).join('')} - -
NameValueCategory
${escapeHtml(v.variableName)}${escapeHtml(v.value)}${escapeHtml(v.category || 'other')}
- ${this.variables.length > 5 ? ` -
- Showing 5 of ${this.variables.length} variables -
- ` : ''} -
- ` : '

No variables defined

'} -
- - -
-

Created: ${new Date(project.createdAt).toLocaleString()}

-

Updated: ${new Date(project.updatedAt).toLocaleString()}

-
-
-
- - -
- - -
-
-
- `; - - // Attach close handlers - document.getElementById('close-modal').addEventListener('click', () => this.close()); - } - - /** - * Render variables management mode - */ - renderVariablesMode() { - const container = document.getElementById('modal-container'); - - container.innerHTML = ` -
-
- -
-
-

Manage Variables

-

${escapeHtml(this.originalProject.name)} (${escapeHtml(this.originalProject.id)})

-
- -
- - -
-
-

${this.variables.length} variable${this.variables.length !== 1 ? 's' : ''} defined

- -
- -
- ${this.variables.length > 0 ? this.variables.map(v => this.renderVariableCard(v)).join('') : ` -
-

No variables defined for this project.

-

Click "Add Variable" to create one.

-
- `} -
-
- - -
- -
-
-
- `; - - // Attach event listeners - document.getElementById('close-modal').addEventListener('click', () => { - this.close(); - // Refresh project list - if (window.loadProjects) window.loadProjects(); - if (window.loadStatistics) window.loadStatistics(); - }); - - document.getElementById('add-variable-btn').addEventListener('click', () => { - this.showVariableForm(); - }); - } - - /** - * Render a single variable card - */ - renderVariableCard(variable) { - return ` -
-
-
-
${escapeHtml(variable.variableName)}
-

${escapeHtml(variable.value)}

- ${variable.description ? `

${escapeHtml(variable.description)}

` : ''} -
- - ${escapeHtml(variable.category || 'other')} - - ${escapeHtml(variable.dataType || 'string')} -
-
-
- - -
-
-
- `; - } - - /** - * Show variable form (add/edit) - */ - showVariableForm(variableName = null) { - const existingVariable = variableName ? this.variables.find(v => v.variableName === variableName) : null; - const isEdit = !!existingVariable; - - const formHtml = ` -
-

${isEdit ? 'Edit' : 'Add'} Variable

-
-
-
- - -

UPPER_SNAKE_CASE format

-
-
- - -
-
-
- - -
-
-
- - -
-
- - -
-
-
- - -
-
-
- `; - - // Insert form - const container = document.querySelector('#variables-list'); - const formContainer = document.createElement('div'); - formContainer.id = 'variable-form-container'; - formContainer.innerHTML = formHtml; - container.insertBefore(formContainer, container.firstChild); - - // Attach event listeners - document.getElementById('variable-form').addEventListener('submit', async (e) => { - e.preventDefault(); - await this.saveVariable(isEdit); - }); - - document.getElementById('cancel-var-btn').addEventListener('click', () => { - document.getElementById('variable-form-container').remove(); - }); - } - - /** - * Save variable (create or update) - */ - async saveVariable(isEdit = false) { - const variableName = document.getElementById('var-name').value.trim(); - const value = document.getElementById('var-value').value.trim(); - const description = document.getElementById('var-description').value.trim(); - const category = document.getElementById('var-category').value; - const dataType = document.getElementById('var-datatype').value; - - if (!variableName || !value) { - showToast('Variable name and value are required', 'error'); - return; - } - - // Validate UPPER_SNAKE_CASE - if (!/^[A-Z][A-Z0-9_]*$/.test(variableName)) { - showToast('Variable name must be UPPER_SNAKE_CASE (e.g., DB_NAME)', 'error'); - return; - } - - try { - const response = await apiRequest(`/api/admin/projects/${this.projectId}/variables`, { - method: 'POST', - body: JSON.stringify({ - variableName, - value, - description, - category, - dataType - }) - }); - - if (response.success) { - showToast(`Variable ${isEdit ? 'updated' : 'created'} successfully`, 'success'); - // Reload variables - const variablesResponse = await apiRequest(`/api/admin/projects/${this.projectId}/variables`); - this.variables = variablesResponse.variables || []; - // Re-render - this.renderVariablesMode(); - } else { - showToast(response.message || 'Failed to save variable', 'error'); - } - } catch (error) { - console.error('Failed to save variable:', error); - showToast('Failed to save variable', 'error'); - } - } - - /** - * Edit variable - */ - editVariable(variableName) { - // Remove any existing form first - const existingForm = document.getElementById('variable-form-container'); - if (existingForm) existingForm.remove(); - - this.showVariableForm(variableName); - } - - /** - * Delete variable - */ - async deleteVariable(variableName) { - if (!confirm(`Delete variable "${variableName}"?`)) { - return; - } - - try { - const response = await apiRequest(`/api/admin/projects/${this.projectId}/variables/${variableName}`, { - method: 'DELETE' - }); - - if (response.success) { - showToast('Variable deleted successfully', 'success'); - // Reload variables - const variablesResponse = await apiRequest(`/api/admin/projects/${this.projectId}/variables`); - this.variables = variablesResponse.variables || []; - // Re-render - this.renderVariablesMode(); - } else { - showToast(response.message || 'Failed to delete variable', 'error'); - } - } catch (error) { - console.error('Failed to delete variable:', error); - showToast('Failed to delete variable', 'error'); - } - } - - /** - * Populate form with project data (edit mode) - */ - populateForm(project) { - document.getElementById('project-id').value = project.id || ''; - document.getElementById('project-name').value = project.name || ''; - document.getElementById('project-description').value = project.description || ''; - document.getElementById('project-active').checked = project.active !== false; - document.getElementById('repo-url').value = project.repositoryUrl || ''; - - if (project.techStack) { - document.getElementById('tech-framework').value = project.techStack.framework || ''; - document.getElementById('tech-database').value = project.techStack.database || ''; - document.getElementById('tech-frontend').value = project.techStack.frontend || ''; - } - } - - /** - * Attach event listeners - */ - attachEventListeners() { - document.getElementById('close-modal').addEventListener('click', () => this.close()); - document.getElementById('cancel-btn').addEventListener('click', () => this.close()); - document.getElementById('save-btn').addEventListener('click', () => this.submit()); - } - - /** - * Submit form - */ - async submit() { - const form = document.getElementById('project-form'); - if (!form.checkValidity()) { - form.reportValidity(); - return; - } - - const projectData = { - id: document.getElementById('project-id').value.trim(), - name: document.getElementById('project-name').value.trim(), - description: document.getElementById('project-description').value.trim(), - active: document.getElementById('project-active').checked, - repositoryUrl: document.getElementById('repo-url').value.trim() || null, - techStack: { - framework: document.getElementById('tech-framework').value.trim() || undefined, - database: document.getElementById('tech-database').value.trim() || undefined, - frontend: document.getElementById('tech-frontend').value.trim() || undefined - } - }; - - try { - let response; - - if (this.mode === 'create') { - response = await apiRequest('/api/admin/projects', { - method: 'POST', - body: JSON.stringify(projectData) - }); - } else { - response = await apiRequest(`/api/admin/projects/${this.projectId}`, { - method: 'PUT', - body: JSON.stringify(projectData) - }); - } - - if (response.success) { - showToast(`Project ${this.mode === 'create' ? 'created' : 'updated'} successfully`, 'success'); - this.close(); - // Refresh project list - if (window.loadProjects) window.loadProjects(); - if (window.loadStatistics) window.loadStatistics(); - } else { - showToast(response.message || 'Failed to save project', 'error'); - } - } catch (error) { - console.error('Failed to save project:', error); - showToast('Failed to save project', 'error'); - } - } - - /** - * Close modal - */ - close() { - const container = document.getElementById('modal-container'); - container.innerHTML = ''; - } -} - -// Utility function -function escapeHtml(text) { - const div = document.createElement('div'); - div.textContent = text; - return div.innerHTML; -} - -// Create global instance -window.projectEditor = new ProjectEditor(); - -// Event delegation for data-action buttons (CSP compliance) -document.addEventListener('click', (e) => { - const button = e.target.closest('[data-action]'); - if (!button) return; - - const action = button.dataset.action; - const arg0 = button.dataset.arg0; - - if (action === 'editVariable') { - window.projectEditor.editVariable(arg0); - } else if (action === 'deleteVariable') { - window.projectEditor.deleteVariable(arg0); - } -}); diff --git a/public/js/admin/project-selector.js b/public/js/admin/project-selector.js deleted file mode 100644 index cd1676cd..00000000 --- a/public/js/admin/project-selector.js +++ /dev/null @@ -1,362 +0,0 @@ -/** - * Project Selector Component - * Reusable dropdown for selecting active project context in admin pages - * - * Features: - * - Loads active projects from API - * - Persists selection to localStorage - * - Emits change events - * - Supports callback functions - * - Responsive design with icons - */ - -class ProjectSelector { - constructor(containerId, options = {}) { - this.containerId = containerId; - this.projects = []; - this.selectedProjectId = null; - - // Options - this.options = { - showAllOption: options.showAllOption !== undefined ? options.showAllOption : true, - allOptionText: options.allOptionText || 'All Projects (Template View)', - onChange: options.onChange || null, - storageKey: options.storageKey || 'selected_project_id', - placeholder: options.placeholder || 'Select a project...', - label: options.label || 'Active Project Context', - showLabel: options.showLabel !== undefined ? options.showLabel : true, - compact: options.compact || false, // Compact mode for navbar - autoLoad: options.autoLoad !== undefined ? options.autoLoad : true - }; - - // Auth token - this.token = localStorage.getItem('admin_token'); - - if (this.options.autoLoad) { - this.init(); - } - } - - /** - * Initialize the component - */ - async init() { - try { - // Load saved project from localStorage - const savedProjectId = localStorage.getItem(this.options.storageKey); - if (savedProjectId) { - this.selectedProjectId = savedProjectId; - } - - // Load projects from API - await this.loadProjects(); - - // Render the selector - this.render(); - - // Attach event listeners - this.attachEventListeners(); - - // Trigger initial change event if project was pre-selected - if (this.selectedProjectId && this.options.onChange) { - this.options.onChange(this.selectedProjectId, this.getSelectedProject()); - } - - } catch (error) { - console.error('Failed to initialize project selector:', error); - this.renderError(); - } - } - - /** - * Load projects from API - */ - async loadProjects() { - const response = await fetch('/api/admin/projects?active=true', { - headers: { - 'Authorization': `Bearer ${this.token}`, - 'Content-Type': 'application/json' - } - }); - - if (response.status === 401) { - localStorage.removeItem('admin_token'); - window.location.href = '/admin/login.html'; - return; - } - - const data = await response.json(); - - if (data.success) { - this.projects = data.projects || []; - - // Sort by name - this.projects.sort((a, b) => a.name.localeCompare(b.name)); - } else { - throw new Error(data.message || 'Failed to load projects'); - } - } - - /** - * Render the selector component - */ - render() { - const container = document.getElementById(this.containerId); - if (!container) { - console.error(`Container #${this.containerId} not found`); - return; - } - - // Determine selected project - const selectedProject = this.getSelectedProject(); - - // Build HTML based on compact or full mode - if (this.options.compact) { - container.innerHTML = this.renderCompact(selectedProject); - } else { - container.innerHTML = this.renderFull(selectedProject); - } - } - - /** - * Render compact mode (for navbar) - */ - renderCompact(selectedProject) { - const displayText = selectedProject ? selectedProject.name : this.options.placeholder; - const displayColor = selectedProject ? 'text-indigo-700' : 'text-gray-500'; - - return ` -
- -
- - - -
-
- `; - } - - /** - * Render full mode (for content area) - */ - renderFull(selectedProject) { - return ` -
- ${this.options.showLabel ? ` - - ` : ''} - - - - ${selectedProject ? ` -
-
-
- - - -
-
-

- ${escapeHtml(selectedProject.name)} -

- ${selectedProject.description ? ` -

- ${escapeHtml(selectedProject.description)} -

- ` : ''} -
- ${selectedProject.variableCount || 0} variable${(selectedProject.variableCount || 0) !== 1 ? 's' : ''} available for substitution -
-
-
-
- ` : ` -
-

- - - - Viewing template text with variable placeholders. Select a project to see rendered values. -

-
- `} -
- `; - } - - /** - * Render error state - */ - renderError() { - const container = document.getElementById(this.containerId); - if (!container) return; - - container.innerHTML = ` -
-
-
- - - -
-
-

- Failed to load projects -

-

- Please refresh the page to try again. -

-
-
-
- `; - } - - /** - * Attach event listeners - */ - attachEventListeners() { - const selectElement = document.getElementById(`${this.containerId}-select`); - if (!selectElement) return; - - selectElement.addEventListener('change', (e) => { - const newProjectId = e.target.value || null; - this.handleChange(newProjectId); - }); - } - - /** - * Handle project selection change - */ - handleChange(projectId) { - const previousProjectId = this.selectedProjectId; - this.selectedProjectId = projectId; - - // Save to localStorage - if (projectId) { - localStorage.setItem(this.options.storageKey, projectId); - } else { - localStorage.removeItem(this.options.storageKey); - } - - // Re-render to update info panel - this.render(); - this.attachEventListeners(); // Re-attach after re-render - - // Trigger callback - if (this.options.onChange) { - const selectedProject = this.getSelectedProject(); - this.options.onChange(projectId, selectedProject, previousProjectId); - } - - // Dispatch custom event for other listeners - const event = new CustomEvent('projectChanged', { - detail: { - projectId, - project: this.getSelectedProject(), - previousProjectId - } - }); - document.dispatchEvent(event); - } - - /** - * Get currently selected project object - */ - getSelectedProject() { - if (!this.selectedProjectId) return null; - return this.projects.find(p => p.id === this.selectedProjectId) || null; - } - - /** - * Get all loaded projects - */ - getProjects() { - return this.projects; - } - - /** - * Programmatically set the selected project - */ - setSelectedProject(projectId) { - this.handleChange(projectId); - } - - /** - * Reload projects from API - */ - async reload() { - try { - await this.loadProjects(); - this.render(); - this.attachEventListeners(); - } catch (error) { - console.error('Failed to reload projects:', error); - this.renderError(); - } - } - - /** - * Get current selection - */ - getSelection() { - return { - projectId: this.selectedProjectId, - project: this.getSelectedProject() - }; - } -} - -/** - * Utility: Escape HTML to prevent XSS - */ -function escapeHtml(text) { - const div = document.createElement('div'); - div.textContent = text; - return div.innerHTML; -} - -// Export for use in other scripts -window.ProjectSelector = ProjectSelector; diff --git a/public/js/admin/rule-editor.js b/public/js/admin/rule-editor.js deleted file mode 100644 index b4138d2f..00000000 --- a/public/js/admin/rule-editor.js +++ /dev/null @@ -1,1111 +0,0 @@ -/** - * Rule Editor Modal - * Handles creation, editing, and viewing of governance rules with real-time validation - * - * @class RuleEditor - * - * @description - * Modal component for rule CRUD operations with these features: - * - Three modes: create, edit, view (read-only) - * - Real-time variable detection (${VAR_NAME} pattern) - * - Live clarity score calculation using heuristics - * - Dynamic example fields (add/remove) - * - Form validation before submission - * - Integration with rule-manager for list refresh - * - * @example - * // Create global instance - * const ruleEditor = new RuleEditor(); - * - * // Open in create mode - * ruleEditor.openCreate(); - * - * // Open in edit mode - * ruleEditor.openEdit('68e8c3a6499d095048311f03'); - * - * // Open in view mode (read-only) - * ruleEditor.openView('68e8c3a6499d095048311f03'); - * - * @property {string} mode - Current mode (create | edit | view) - * @property {string} ruleId - MongoDB ObjectId of rule being edited - * @property {Object} originalRule - Original rule data (for edit mode) - * @property {Array} detectedVariables - Variables detected in rule text - */ - -class RuleEditor { - constructor() { - this.mode = 'create'; // 'create' or 'edit' - this.ruleId = null; - this.originalRule = null; - this.detectedVariables = []; - } - - /** - * Open editor in create mode - */ - openCreate() { - this.mode = 'create'; - this.ruleId = null; - this.originalRule = null; - this.detectedVariables = []; - this.render(); - this.attachEventListeners(); - } - - /** - * Open editor in edit mode - */ - async openEdit(ruleId) { - this.mode = 'edit'; - this.ruleId = ruleId; - - try { - const response = await apiRequest(`/api/admin/rules/${ruleId}`); - - if (!response.success || !response.rule) { - throw new Error('Failed to load rule'); - } - - this.originalRule = response.rule; - this.detectedVariables = response.rule.variables || []; - this.render(); - this.populateForm(response.rule); - this.attachEventListeners(); - } catch (error) { - console.error('Failed to load rule:', error); - showToast('Failed to load rule for editing', 'error'); - } - } - - /** - * Open editor in view mode (read-only) - */ - async openView(ruleId) { - this.mode = 'view'; - this.ruleId = ruleId; - - try { - const response = await apiRequest(`/api/admin/rules/${ruleId}`); - - if (!response.success || !response.rule) { - throw new Error('Failed to load rule'); - } - - this.originalRule = response.rule; - this.detectedVariables = response.rule.variables || []; - this.renderViewMode(response.rule); - } catch (error) { - console.error('Failed to load rule:', error); - showToast('Failed to load rule', 'error'); - } - } - - /** - * Render the editor modal - */ - render() { - const container = document.getElementById('modal-container'); - const title = this.mode === 'create' ? 'Create New Rule' : 'Edit Rule'; - - container.innerHTML = ` -
-
- -
-

${title}

- -
- - -
-
-
- -
- -
- - -

Unique identifier (e.g., inst_019, inst_020)

-
- - -
- - -

Use \${VARIABLE_NAME} for dynamic values

-
- - - - - -
- -
- -
- -
- - -
- - -
-
- - -
- -
- - -

Universal rules apply to all projects

-
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- Low (0) - High (100) -
-
- - -
- - -
- - -
- - -
- - -
- -
-
-
-
- 100 -
-

- Based on language strength and specificity -

-
- - - ${this.mode === 'edit' ? ` -
-
- - - - - -
- -

- Get AI-powered suggestions to improve clarity, specificity, and actionability -

-
- - - - ` : ''} -
-
-
-
- - -
- - -
-
-
- `; - } - - /** - * Render view-only mode - */ - renderViewMode(rule) { - const container = document.getElementById('modal-container'); - - container.innerHTML = ` -
-
- -
-
-

Rule Details

-

${rule.id}

-
- -
- - -
- -
- - ${rule.scope} - - - ${rule.quadrant} - - - ${rule.persistence} - - - ${rule.validationStatus} - -
- - -
- -
${this.escapeHtml(rule.text)}
-
- - - ${rule.variables && rule.variables.length > 0 ? ` -
- -
- ${rule.variables.map(v => ` - - \${${v}} - - `).join('')} -
-
- ` : ''} - - -
-
- -

${rule.category}

-
-
- -

${rule.priority}

-
-
- -

${rule.temporalScope}

-
-
- -

${rule.active ? 'Active' : 'Inactive'}

-
-
- - - ${rule.clarityScore !== null ? ` -
- -
-
-
- Clarity - ${rule.clarityScore}% -
-
-
-
-
- ${rule.specificityScore !== null ? ` -
-
- Specificity - ${rule.specificityScore}% -
-
-
-
-
- ` : ''} - ${rule.actionabilityScore !== null ? ` -
-
- Actionability - ${rule.actionabilityScore}% -
-
-
-
-
- ` : ''} -
-
- ` : ''} - - - ${rule.notes ? ` -
- -
${this.escapeHtml(rule.notes)}
-
- ` : ''} - - -
-
-
- Created: - ${this.formatDate(rule.createdAt)} -
-
- Updated: - ${this.formatDate(rule.updatedAt)} -
-
- Created by: - ${rule.createdBy} -
-
- Source: - ${rule.source} -
-
-
-
- - -
- - -
-
-
- `; - - // Attach close handler - document.querySelectorAll('#close-modal').forEach(btn => { - btn.addEventListener('click', () => this.close()); - }); - } - - /** - * Populate form with existing rule data (edit mode) - */ - populateForm(rule) { - document.getElementById('rule-id').value = rule.id; - document.getElementById('rule-text').value = rule.text; - document.getElementById('rule-scope').value = rule.scope; - document.getElementById('rule-quadrant').value = rule.quadrant; - document.getElementById('rule-persistence').value = rule.persistence; - document.getElementById('rule-category').value = rule.category || 'other'; - document.getElementById('rule-priority').value = rule.priority || 50; - document.getElementById('priority-value').textContent = rule.priority || 50; - document.getElementById('rule-temporal').value = rule.temporalScope || 'PERMANENT'; - document.getElementById('rule-active').checked = rule.active !== false; - document.getElementById('rule-notes').value = rule.notes || ''; - - // Populate examples if any - if (rule.examples && rule.examples.length > 0) { - rule.examples.forEach(example => { - this.addExampleField(example); - }); - } - - // Trigger variable detection - this.detectVariables(); - this.calculateClarityScore(); - } - - /** - * Attach event listeners - */ - attachEventListeners() { - // Close modal - document.querySelectorAll('#close-modal, #cancel-btn').forEach(btn => { - btn.addEventListener('click', () => this.close()); - }); - - // Variable detection on text change - document.getElementById('rule-text').addEventListener('input', () => { - this.detectVariables(); - this.calculateClarityScore(); - }); - - // Priority slider - document.getElementById('rule-priority').addEventListener('input', (e) => { - document.getElementById('priority-value').textContent = e.target.value; - }); - - // Add example button - document.getElementById('add-example').addEventListener('click', () => { - this.addExampleField(); - }); - - // AI Optimization (edit mode only) - if (this.mode === 'edit') { - const optimizeBtn = document.getElementById('optimize-rule-btn'); - if (optimizeBtn) { - optimizeBtn.addEventListener('click', () => this.runOptimization()); - } - - const applyBtn = document.getElementById('apply-optimization-btn'); - if (applyBtn) { - applyBtn.addEventListener('click', () => this.applyOptimization()); - } - } - - // Form submission - document.getElementById('save-btn').addEventListener('click', (e) => { - e.preventDefault(); - this.saveRule(); - }); - } - - /** - * Detect variables in rule text - */ - detectVariables() { - const text = document.getElementById('rule-text').value; - const varPattern = /\$\{([A-Z_]+)\}/g; - const variables = []; - let match; - - while ((match = varPattern.exec(text)) !== null) { - if (!variables.includes(match[1])) { - variables.push(match[1]); - } - } - - this.detectedVariables = variables; - - // Update UI - const section = document.getElementById('variables-section'); - const list = document.getElementById('variables-list'); - - if (variables.length > 0) { - section.classList.remove('hidden'); - list.innerHTML = variables.map(v => ` - - \${${v}} - - `).join(''); - } else { - section.classList.add('hidden'); - } - } - - /** - * Calculate clarity score (heuristic) - */ - calculateClarityScore() { - const text = document.getElementById('rule-text').value; - let score = 100; - - if (!text) { - score = 0; - } else { - // Deduct for weak language - const weakWords = ['try', 'maybe', 'consider', 'might', 'probably', 'possibly', 'perhaps']; - weakWords.forEach(word => { - if (new RegExp(`\\b${word}\\b`, 'i').test(text)) { - score -= 10; - } - }); - - // Bonus for strong imperatives - const strongWords = ['MUST', 'SHALL', 'REQUIRED', 'PROHIBITED', 'NEVER']; - const hasStrong = strongWords.some(word => new RegExp(`\\b${word}\\b`).test(text)); - if (!hasStrong) score -= 10; - - // Bonus for specificity (has numbers or variables) - if (!/\d/.test(text) && !/\$\{[A-Z_]+\}/.test(text)) { - score -= 5; - } - } - - score = Math.max(0, Math.min(100, score)); - - // Update UI - document.getElementById('clarity-score').textContent = score; - const bar = document.getElementById('clarity-bar'); - bar.style.width = `${score}%`; - bar.className = `h-2 rounded-full transition-all ${ - score >= 80 ? 'bg-green-500' : score >= 60 ? 'bg-yellow-500' : 'bg-red-500' - }`; - } - - /** - * Add example field - */ - addExampleField(value = '') { - const list = document.getElementById('examples-list'); - const index = list.children.length; - - const div = document.createElement('div'); - div.className = 'flex space-x-2'; - div.innerHTML = ` - - - `; - - list.appendChild(div); - } - - /** - * Save rule (create or update) - */ - async saveRule() { - const form = document.getElementById('rule-form'); - - // Get form data - const formData = { - id: document.getElementById('rule-id').value.trim(), - text: document.getElementById('rule-text').value.trim(), - scope: document.getElementById('rule-scope').value, - quadrant: document.getElementById('rule-quadrant').value, - persistence: document.getElementById('rule-persistence').value, - category: document.getElementById('rule-category').value, - priority: parseInt(document.getElementById('rule-priority').value), - temporalScope: document.getElementById('rule-temporal').value, - active: document.getElementById('rule-active').checked, - notes: document.getElementById('rule-notes').value.trim() - }; - - // Collect examples - const exampleInputs = document.querySelectorAll('[name^="example-"]'); - formData.examples = Array.from(exampleInputs) - .map(input => input.value.trim()) - .filter(val => val.length > 0); - - // Validation - if (!formData.id) { - showToast('Rule ID is required', 'error'); - return; - } - if (!formData.text) { - showToast('Rule text is required', 'error'); - return; - } - if (!formData.quadrant) { - showToast('Quadrant is required', 'error'); - return; - } - if (!formData.persistence) { - showToast('Persistence is required', 'error'); - return; - } - - // Save - try { - const saveBtn = document.getElementById('save-btn'); - saveBtn.disabled = true; - saveBtn.textContent = 'Saving...'; - - let response; - if (this.mode === 'create') { - response = await apiRequest('/api/admin/rules', { - method: 'POST', - body: JSON.stringify(formData) - }); - } else { - response = await apiRequest(`/api/admin/rules/${this.ruleId}`, { - method: 'PUT', - body: JSON.stringify(formData) - }); - } - - if (response.success) { - showToast( - this.mode === 'create' ? 'Rule created successfully' : 'Rule updated successfully', - 'success' - ); - this.close(); - // Refresh the rules list - if (typeof loadRules === 'function') loadRules(); - if (typeof loadStatistics === 'function') loadStatistics(); - } else { - throw new Error(response.message || 'Failed to save rule'); - } - } catch (error) { - console.error('Save error:', error); - showToast(error.message || 'Failed to save rule', 'error'); - - const saveBtn = document.getElementById('save-btn'); - saveBtn.disabled = false; - saveBtn.textContent = this.mode === 'create' ? 'Create Rule' : 'Save Changes'; - } - } - - /** - * Close the modal - */ - close() { - const container = document.getElementById('modal-container'); - container.innerHTML = ''; - } - - // Utility methods - escapeHtml(text) { - const div = document.createElement('div'); - div.textContent = text; - return div.innerHTML; - } - - formatDate(dateString) { - if (!dateString) return 'Unknown'; - const date = new Date(dateString); - return date.toLocaleString('en-US', { - year: 'numeric', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' - }); - } - - getQuadrantColor(quadrant) { - const colors = { - STRATEGIC: 'bg-purple-100 text-purple-800', - OPERATIONAL: 'bg-green-100 text-green-800', - TACTICAL: 'bg-yellow-100 text-yellow-800', - SYSTEM: 'bg-blue-100 text-blue-800', - STORAGE: 'bg-gray-100 text-gray-800' - }; - return colors[quadrant] || 'bg-gray-100 text-gray-800'; - } - - getPersistenceColor(persistence) { - const colors = { - HIGH: 'bg-red-100 text-red-800', - MEDIUM: 'bg-orange-100 text-orange-800', - LOW: 'bg-yellow-100 text-yellow-800' - }; - return colors[persistence] || 'bg-gray-100 text-gray-800'; - } - - getValidationColor(status) { - const colors = { - PASSED: 'bg-green-100 text-green-800', - FAILED: 'bg-red-100 text-red-800', - NEEDS_REVIEW: 'bg-yellow-100 text-yellow-800', - NOT_VALIDATED: 'bg-gray-100 text-gray-800' - }; - return colors[status] || 'bg-gray-100 text-gray-800'; - } - - /** - * Run AI optimization analysis - */ - async runOptimization() { - if (!this.ruleId) return; - - const optimizeBtn = document.getElementById('optimize-rule-btn'); - const resultsSection = document.getElementById('optimization-results'); - - try { - // Show loading state - optimizeBtn.disabled = true; - optimizeBtn.innerHTML = ` - - - - - `; - - // Call optimization API - const response = await apiRequest(`/api/admin/rules/${this.ruleId}/optimize`, { - method: 'POST', - body: JSON.stringify({ mode: 'aggressive' }) - }); - - if (!response.success) { - throw new Error(response.message || 'Optimization failed'); - } - - // Store optimization result - this.optimizationResult = response; - - // Display results - this.displayOptimizationResults(response); - - // Show results section - resultsSection.classList.remove('hidden'); - - showToast('Analysis complete', 'success'); - - } catch (error) { - console.error('Optimization error:', error); - showToast(error.message || 'Failed to run optimization', 'error'); - } finally { - optimizeBtn.disabled = false; - optimizeBtn.textContent = 'Analyze & Optimize'; - } - } - - /** - * Display optimization results in UI - */ - displayOptimizationResults(result) { - const { analysis, optimization } = result; - - // Update score bars - this.updateScoreBar('ai-clarity', analysis.clarity.score, analysis.clarity.grade); - this.updateScoreBar('ai-specificity', analysis.specificity.score, analysis.specificity.grade); - this.updateScoreBar('ai-actionability', analysis.actionability.score, analysis.actionability.grade); - - // Display suggestions - const suggestionsList = document.getElementById('suggestions-list'); - const allIssues = [ - ...analysis.clarity.issues, - ...analysis.specificity.issues, - ...analysis.actionability.issues - ]; - - if (allIssues.length > 0) { - suggestionsList.innerHTML = allIssues.map((issue, index) => ` -
- - ${index + 1} - - ${this.escapeHtml(issue)} -
- `).join(''); - } else { - suggestionsList.innerHTML = ` -
- ✓ No issues found - this rule is well-formed! -
- `; - } - - // Show/hide apply button based on whether there are optimizations - const applySection = document.getElementById('auto-optimize-section'); - if (optimization.optimizedText !== result.rule.originalText) { - applySection.classList.remove('hidden'); - } else { - applySection.classList.add('hidden'); - } - } - - /** - * Update score bar visualization - */ - updateScoreBar(prefix, score, grade) { - const scoreElement = document.getElementById(`${prefix}-score`); - const barElement = document.getElementById(`${prefix}-bar`); - - scoreElement.textContent = `${score} (${grade})`; - barElement.style.width = `${score}%`; - - // Update color based on score - const colorClass = score >= 80 ? 'bg-green-500' : score >= 60 ? 'bg-yellow-500' : 'bg-red-500'; - barElement.className = `h-1.5 rounded-full transition-all ${colorClass}`; - } - - /** - * Apply AI optimizations to rule text - */ - async applyOptimization() { - if (!this.optimizationResult) return; - - const { optimization } = this.optimizationResult; - const ruleTextArea = document.getElementById('rule-text'); - - // Confirm with user - if (!confirm('Apply AI optimizations to rule text? This will overwrite your current text.')) { - return; - } - - // Update text area - ruleTextArea.value = optimization.optimizedText; - - // Trigger variable detection and clarity recalculation - this.detectVariables(); - this.calculateClarityScore(); - - // Hide results and reset - document.getElementById('optimization-results').classList.add('hidden'); - this.optimizationResult = null; - - showToast(`Applied ${optimization.changes.length} optimization(s)`, 'success'); - } -} - -// Create global instance -window.ruleEditor = new RuleEditor(); - // Set widths/heights from data attributes (CSP compliance) - function setProgressBarWidths(container) { - const elements = container.querySelectorAll('[data-width], [data-height]'); - elements.forEach(el => { - if (el.dataset.width) el.style.width = el.dataset.width + '%'; - if (el.dataset.height) el.style.height = el.dataset.height + '%'; - }); - } - -// Event delegation for data-action buttons (CSP compliance) -document.addEventListener('click', (e) => { - const button = e.target.closest('[data-action]'); - if (!button) return; - - const action = button.dataset.action; - const arg0 = button.dataset.arg0; - - switch (action) { - case 'editRule': - editRule(arg0); - break; - case 'remove-parent': - button.parentElement.remove(); - break; - } -}); diff --git a/public/js/admin/rule-manager.js b/public/js/admin/rule-manager.js deleted file mode 100644 index 77d8125d..00000000 --- a/public/js/admin/rule-manager.js +++ /dev/null @@ -1,706 +0,0 @@ -/** - * Rule Manager - Multi-Project Governance Dashboard - * Handles filtering, sorting, pagination, and CRUD operations for rules - */ - -// Auth check -const token = localStorage.getItem('admin_token'); -const user = JSON.parse(localStorage.getItem('admin_user') || '{}'); - -if (!token) { - window.location.href = '/admin/login.html'; -} - -// Display admin name -document.getElementById('admin-name').textContent = user.email || 'Admin'; - -// Logout -document.getElementById('logout-btn').addEventListener('click', () => { - localStorage.removeItem('admin_token'); - localStorage.removeItem('admin_user'); - window.location.href = '/admin/login.html'; -}); - -/** - * API request helper with automatic auth header injection and token refresh - * - * @param {string} endpoint - API endpoint path (e.g., '/api/admin/rules') - * @param {Object} [options={}] - Fetch options (method, body, headers, etc.) - * @returns {Promise} JSON response from API - * - * @description - * - Automatically adds Authorization header with Bearer token - * - Redirects to login on 401 (unauthorized) - * - Handles JSON response parsing - */ -async function apiRequest(endpoint, options = {}) { - const response = await fetch(endpoint, { - ...options, - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json', - ...options.headers - } - }); - - if (response.status === 401) { - localStorage.removeItem('admin_token'); - window.location.href = '/admin/login.html'; - return; - } - - return response.json(); -} - -// State management -let currentPage = 1; -const pageSize = 20; -let totalRules = 0; -let selectedProjectId = null; // Track selected project for variable substitution -let filters = { - scope: '', - quadrant: '', - persistence: '', - validation: '', - active: 'true', - search: '', - sort: 'priority', - order: 'desc' -}; - -/** - * Load and display dashboard statistics - * Fetches rule counts, validation status, and average clarity scores - * - * @async - * @description - * Updates the following stat cards: - * - Total rules - * - Universal rules count - * - Validated rules count - * - Average clarity score - */ -async function loadStatistics() { - try { - const response = await apiRequest('/api/admin/rules/stats'); - - if (!response.success || !response.stats) { - console.error('Invalid stats response:', response); - return; - } - - const stats = response.stats; - - document.getElementById('stat-total').textContent = stats.total || 0; - document.getElementById('stat-universal').textContent = stats.byScope?.UNIVERSAL || 0; - document.getElementById('stat-validated').textContent = stats.byValidationStatus?.PASSED || 0; - - const avgClarity = stats.averageScores?.clarity; - document.getElementById('stat-clarity').textContent = avgClarity ? avgClarity.toFixed(0) + '%' : 'N/A'; - } catch (error) { - console.error('Failed to load statistics:', error); - showToast('Failed to load statistics', 'error'); - } -} - -/** - * Load and render rules based on current filters, sorting, and pagination - * - * @async - * @description - * - Builds query parameters from current filter state - * - Fetches rules from API - * - Renders rule cards in grid layout - * - Updates pagination UI - * - Shows loading/empty/error states - * - * @fires loadRules - Called on filter change, sort change, or page change - */ -async function loadRules() { - const container = document.getElementById('rules-grid'); - - try { - // Show loading state - container.innerHTML = ` -
-
-

Loading rules...

-
- `; setProgressBarWidths(container); - - // Build query parameters - const params = new URLSearchParams({ - page: currentPage, - limit: pageSize, - sort: filters.sort, - order: filters.order - }); - - if (filters.scope) params.append('scope', filters.scope); - if (filters.quadrant) params.append('quadrant', filters.quadrant); - if (filters.persistence) params.append('persistence', filters.persistence); - if (filters.validation) params.append('validationStatus', filters.validation); - if (filters.active) params.append('active', filters.active); - if (filters.search) params.append('search', filters.search); - - // Include project ID for variable substitution - if (selectedProjectId) params.append('projectId', selectedProjectId); - - const response = await apiRequest(`/api/admin/rules?${params.toString()}`); - - if (!response.success) { - throw new Error('Failed to load rules'); - } - - const rules = response.rules || []; - totalRules = response.pagination?.total || 0; - - // Update results count - document.getElementById('filter-results').textContent = - `Showing ${rules.length} of ${totalRules} rules`; - - // Render rules - if (rules.length === 0) { - container.innerHTML = ` -
- - - -

No rules found

-

Try adjusting your filters or create a new rule.

-
- `; setProgressBarWidths(container); - document.getElementById('pagination').classList.add('hidden'); - return; - } - - // Render rule cards - container.innerHTML = ` -
- ${rules.map(rule => renderRuleCard(rule)).join('')} -
- `; setProgressBarWidths(container); - - // Update pagination - updatePagination(response.pagination); - - } catch (error) { - console.error('Failed to load rules:', error); - container.innerHTML = ` -
-

Failed to load rules. Please try again.

-
- `; setProgressBarWidths(container); - showToast('Failed to load rules', 'error'); - } -} - -/** - * Render a single rule as an HTML card - * - * @param {Object} rule - Rule object from API - * @param {string} rule._id - MongoDB ObjectId - * @param {string} rule.id - Rule ID (inst_xxx) - * @param {string} rule.text - Rule text - * @param {string} rule.scope - UNIVERSAL | PROJECT_SPECIFIC - * @param {string} rule.quadrant - STRATEGIC | OPERATIONAL | TACTICAL | SYSTEM | STORAGE - * @param {string} rule.persistence - HIGH | MEDIUM | LOW - * @param {number} rule.priority - Priority (0-100) - * @param {number} [rule.clarityScore] - Clarity score (0-100) - * @param {Array} [rule.variables] - Detected variables - * @param {Object} [rule.usageStats] - Usage statistics - * - * @returns {string} HTML string for rule card - * - * @description - * Generates a card with: - * - Scope, quadrant, persistence, validation status badges - * - Rule text (truncated to 2 lines) - * - Priority, variable count, enforcement count - * - Clarity score progress bar - * - View/Edit/Delete action buttons - */ -function renderRuleCard(rule) { - const scopeBadgeColor = rule.scope === 'UNIVERSAL' ? 'bg-blue-100 text-blue-800' : 'bg-gray-100 text-gray-800'; - const quadrantBadgeColor = getQuadrantColor(rule.quadrant); - const persistenceBadgeColor = getPersistenceColor(rule.persistence); - const validationBadgeColor = getValidationColor(rule.validationStatus); - const clarityScore = rule.clarityScore || 0; - const clarityColor = clarityScore >= 80 ? 'bg-green-500' : clarityScore >= 60 ? 'bg-yellow-500' : 'bg-red-500'; - - return ` -
-
-
- - ${rule.scope} - - - ${rule.quadrant} - - - ${rule.persistence} - - ${rule.validationStatus !== 'NOT_VALIDATED' ? ` - - ${rule.validationStatus} - - ` : ''} -
- ${rule.id} -
- - ${rule.renderedText ? ` - -
-
- - - - Template -
-

${escapeHtml(rule.text)}

-
- - -
-
- - - - Rendered (${rule.projectContext || 'Unknown'}) -
-

${escapeHtml(rule.renderedText)}

-
- ` : ` - -

${escapeHtml(rule.text)}

- `} - -
-
-
- - - - Priority: ${rule.priority} -
- ${rule.variables && rule.variables.length > 0 ? ` -
- - - - ${rule.variables.length} var${rule.variables.length !== 1 ? 's' : ''} -
- ` : ''} - ${rule.usageStats?.timesEnforced > 0 ? ` -
- - - - ${rule.usageStats.timesEnforced} enforcements -
- ` : ''} -
- - ${rule.clarityScore !== null ? ` -
- Clarity: -
-
-
- ${clarityScore}% -
- ` : ''} -
- -
- - - -
-
- `; -} - -/** - * Update pagination UI with page numbers and navigation buttons - * - * @param {Object} pagination - Pagination metadata from API - * @param {number} pagination.page - Current page number - * @param {number} pagination.limit - Items per page - * @param {number} pagination.total - Total number of items - * @param {number} pagination.pages - Total number of pages - * - * @description - * - Shows/hides pagination based on total items - * - Generates smart page number buttons (shows first, last, and pages around current) - * - Adds ellipsis (...) for gaps in page numbers - * - Enables/disables prev/next buttons based on current page - */ -function updatePagination(pagination) { - const paginationDiv = document.getElementById('pagination'); - - if (!pagination || pagination.total === 0) { - paginationDiv.classList.add('hidden'); - return; - } - - paginationDiv.classList.remove('hidden'); - - const start = (pagination.page - 1) * pagination.limit + 1; - const end = Math.min(pagination.page * pagination.limit, pagination.total); - - document.getElementById('page-start').textContent = start; - document.getElementById('page-end').textContent = end; - document.getElementById('page-total').textContent = pagination.total; - - // Update page buttons - const prevBtn = document.getElementById('prev-page'); - const nextBtn = document.getElementById('next-page'); - - prevBtn.disabled = pagination.page <= 1; - nextBtn.disabled = pagination.page >= pagination.pages; - - // Generate page numbers - const pageNumbers = document.getElementById('page-numbers'); - const pages = []; - const currentPage = pagination.page; - const totalPages = pagination.pages; - - // Always show first page - pages.push(1); - - // Show pages around current page - for (let i = Math.max(2, currentPage - 1); i <= Math.min(totalPages - 1, currentPage + 1); i++) { - if (!pages.includes(i)) pages.push(i); - } - - // Always show last page - if (totalPages > 1 && !pages.includes(totalPages)) { - pages.push(totalPages); - } - - pageNumbers.innerHTML = pages.map((page, index) => { - const prev = pages[index - 1]; - const gap = prev && page - prev > 1 ? '...' : ''; - const active = page === currentPage ? 'bg-indigo-600 text-white' : 'border border-gray-300 text-gray-700 hover:bg-gray-50'; - - return ` - ${gap} - - `; - }).join(''); -} - -// Pagination handlers -function goToPage(page) { - currentPage = page; - loadRules(); - window.scrollTo({ top: 0, behavior: 'smooth' }); -} - -document.getElementById('prev-page')?.addEventListener('click', () => { - if (currentPage > 1) { - goToPage(currentPage - 1); - } -}); - -document.getElementById('next-page')?.addEventListener('click', () => { - const maxPage = Math.ceil(totalRules / pageSize); - if (currentPage < maxPage) { - goToPage(currentPage + 1); - } -}); - -// Filter handlers -function applyFilters() { - currentPage = 1; // Reset to first page when filters change - loadRules(); -} - -document.getElementById('filter-scope')?.addEventListener('change', (e) => { - filters.scope = e.target.value; - applyFilters(); -}); - -document.getElementById('filter-quadrant')?.addEventListener('change', (e) => { - filters.quadrant = e.target.value; - applyFilters(); -}); - -document.getElementById('filter-persistence')?.addEventListener('change', (e) => { - filters.persistence = e.target.value; - applyFilters(); -}); - -document.getElementById('filter-validation')?.addEventListener('change', (e) => { - filters.validation = e.target.value; - applyFilters(); -}); - -document.getElementById('filter-active')?.addEventListener('change', (e) => { - filters.active = e.target.value; - applyFilters(); -}); - -document.getElementById('sort-by')?.addEventListener('change', (e) => { - filters.sort = e.target.value; - applyFilters(); -}); - -document.getElementById('sort-order')?.addEventListener('change', (e) => { - filters.order = e.target.value; - applyFilters(); -}); - -// Search with debouncing -let searchTimeout; -document.getElementById('search-box')?.addEventListener('input', (e) => { - clearTimeout(searchTimeout); - searchTimeout = setTimeout(() => { - filters.search = e.target.value; - applyFilters(); - }, 500); // 500ms debounce -}); - -// Clear filters -document.getElementById('clear-filters-btn')?.addEventListener('click', () => { - filters = { - scope: '', - quadrant: '', - persistence: '', - validation: '', - active: 'true', - search: '', - sort: 'priority', - order: 'desc' - }; - - document.getElementById('filter-scope').value = ''; - document.getElementById('filter-quadrant').value = ''; - document.getElementById('filter-persistence').value = ''; - document.getElementById('filter-validation').value = ''; - document.getElementById('filter-active').value = 'true'; - document.getElementById('search-box').value = ''; - document.getElementById('sort-by').value = 'priority'; - document.getElementById('sort-order').value = 'desc'; - - applyFilters(); -}); - -// CRUD operations -async function viewRule(ruleId) { - if (window.ruleEditor) { - window.ruleEditor.openView(ruleId); - } else { - showToast('Rule editor not loaded', 'error'); - } -} - -async function editRule(ruleId) { - if (window.ruleEditor) { - window.ruleEditor.openEdit(ruleId); - } else { - showToast('Rule editor not loaded', 'error'); - } -} - -async function deleteRule(ruleId, ruleName) { - if (!confirm(`Delete rule "${ruleName}"? This will deactivate the rule (soft delete).`)) { - return; - } - - try { - const response = await apiRequest(`/api/admin/rules/${ruleId}`, { - method: 'DELETE' - }); - - if (response.success) { - showToast('Rule deleted successfully', 'success'); - loadRules(); - loadStatistics(); - } else { - showToast(response.message || 'Failed to delete rule', 'error'); - } - } catch (error) { - console.error('Delete error:', error); - showToast('Failed to delete rule', 'error'); - } -} - -// New rule button -document.getElementById('new-rule-btn')?.addEventListener('click', () => { - if (window.ruleEditor) { - window.ruleEditor.openCreate(); - } else { - showToast('Rule editor not loaded', 'error'); - } -}); - -/** - * Show a toast notification message - * - * @param {string} message - Message to display - * @param {string} [type='info'] - Toast type (success | error | warning | info) - * - * @description - * - Creates animated toast notification in top-right corner - * - Auto-dismisses after 5 seconds - * - Can be manually dismissed by clicking X button - * - Color-coded by type (green=success, red=error, yellow=warning, blue=info) - */ -function showToast(message, type = 'info') { - const container = document.getElementById('toast-container'); - const colors = { - success: 'bg-green-500', - error: 'bg-red-500', - warning: 'bg-yellow-500', - info: 'bg-blue-500' - }; - - const toast = document.createElement('div'); - toast.className = `${colors[type]} text-white px-6 py-3 rounded-lg shadow-lg flex items-center space-x-2 transition-all duration-300 ease-in-out`; - toast.style.opacity = '0'; - toast.style.transform = 'translateX(100px)'; - toast.innerHTML = ` - ${escapeHtml(message)} - - `; - - container.appendChild(toast); - - // Trigger animation - setTimeout(() => { - toast.style.opacity = '1'; - toast.style.transform = 'translateX(0)'; - }, 10); - - // Auto-remove after 5 seconds - setTimeout(() => { - toast.style.opacity = '0'; - toast.style.transform = 'translateX(100px)'; - setTimeout(() => toast.remove(), 300); - }, 5000); -} - -// Utility functions -function getQuadrantColor(quadrant) { - const colors = { - STRATEGIC: 'bg-purple-100 text-purple-800', - OPERATIONAL: 'bg-green-100 text-green-800', - TACTICAL: 'bg-yellow-100 text-yellow-800', - SYSTEM: 'bg-blue-100 text-blue-800', - STORAGE: 'bg-gray-100 text-gray-800' - }; - return colors[quadrant] || 'bg-gray-100 text-gray-800'; -} - -function getPersistenceColor(persistence) { - const colors = { - HIGH: 'bg-red-100 text-red-800', - MEDIUM: 'bg-orange-100 text-orange-800', - LOW: 'bg-yellow-100 text-yellow-800' - }; - return colors[persistence] || 'bg-gray-100 text-gray-800'; -} - -function getValidationColor(status) { - const colors = { - PASSED: 'bg-green-100 text-green-800', - FAILED: 'bg-red-100 text-red-800', - NEEDS_REVIEW: 'bg-yellow-100 text-yellow-800', - NOT_VALIDATED: 'bg-gray-100 text-gray-800' - }; - return colors[status] || 'bg-gray-100 text-gray-800'; -} - -function escapeHtml(text) { - const div = document.createElement('div'); - div.textContent = text; - return div.innerHTML; -} - -// Make functions global for onclick handlers -window.viewRule = viewRule; -window.editRule = editRule; -window.deleteRule = deleteRule; -window.goToPage = goToPage; - -/** - * Initialize project selector for variable substitution - * When a project is selected, rules will show both template and rendered text - */ -const projectSelector = new ProjectSelector('project-selector-container', { - showAllOption: true, - allOptionText: 'All Projects (Template View)', - label: 'Project Context for Variable Substitution', - showLabel: true, - compact: false, - onChange: (projectId, project) => { - // Update selected project state - selectedProjectId = projectId; - - // Reload rules with new project context - currentPage = 1; // Reset to first page - loadRules(); - - // Show toast notification - if (projectId && project) { - showToast(`Viewing rules with ${project.name} context`, 'info'); - } else { - showToast('Viewing template rules (no variable substitution)', 'info'); - } - } -}); - -// Initialize on page load -loadStatistics(); -loadRules(); - // Set widths/heights from data attributes (CSP compliance) - function setProgressBarWidths(container) { - const elements = container.querySelectorAll('[data-width], [data-height]'); - elements.forEach(el => { - if (el.dataset.width) el.style.width = el.dataset.width + '%'; - if (el.dataset.height) el.style.height = el.dataset.height + '%'; - }); - } - -// Event delegation for data-action buttons (CSP compliance) -document.addEventListener('click', (e) => { - const button = e.target.closest('[data-action]'); - if (!button) return; - - const action = button.dataset.action; - const arg0 = button.dataset.arg0; - const arg1 = button.dataset.arg1; - - switch (action) { - case 'viewRule': - viewRule(arg0); - break; - case 'editRule': - editRule(arg0); - break; - case 'deleteRule': - deleteRule(arg0, arg1); - break; - case 'goToPage': - goToPage(parseInt(arg0)); - break; - case 'remove-parent': - button.parentElement.remove(); - break; - } -}); - diff --git a/public/js/components/interactive-diagram.js b/public/js/components/interactive-diagram.js deleted file mode 100644 index f9cf24cf..00000000 --- a/public/js/components/interactive-diagram.js +++ /dev/null @@ -1,359 +0,0 @@ -/** - * Interactive Architecture Diagram Component - * Tractatus Framework - Phase 3: Interactive Architecture Diagram - * - * Handles click/hover interactions on the hexagonal service diagram - * Shows service details in a side panel - */ - -class InteractiveDiagram { - constructor() { - this.serviceData = { - overview: { - name: 'Tractatus Governance Layer', - shortName: 'Overview', - color: '#0ea5e9', - icon: '⚙️', - description: 'Six external governance services working together to enforce AI safety boundaries outside the AI runtime.', - details: [ - 'All services operate externally to the AI—making manipulation harder', - 'Instruction storage and validation work together to prevent directive fade', - 'Boundary enforcement and deliberation coordinate on values decisions', - 'Pressure monitoring adjusts verification requirements dynamically', - 'Metacognitive gates ensure AI pauses before high-risk operations', - 'Each service addresses a different failure mode in AI safety' - ], - promise: 'External architectural enforcement that is structurally more difficult to bypass than behavioral training alone.' - }, - boundary: { - name: 'BoundaryEnforcer', - shortName: 'Boundary', - color: '#10b981', - icon: '🔒', - description: 'Blocks AI from making values decisions (privacy, ethics, strategic direction). Requires human approval.', - details: [ - 'Enforces Tractatus 12.1-12.7 boundaries', - 'Values decisions architecturally require humans', - 'Prevents AI autonomous decision-making on ethical questions', - 'External enforcement - harder to bypass via prompting' - ], - promise: 'Values boundaries enforced externally—harder to manipulate through prompting.' - }, - instruction: { - name: 'InstructionPersistenceClassifier', - shortName: 'Instruction', - color: '#6366f1', - icon: '📋', - description: 'Stores instructions externally with persistence levels (HIGH/MEDIUM/LOW). Aims to reduce directive fade.', - details: [ - 'Quadrant-based classification (STR/OPS/TAC/SYS/STO)', - 'Time-persistence metadata tagging', - 'Temporal horizon modeling (STRATEGIC, OPERATIONAL, TACTICAL)', - 'External storage independent of AI runtime' - ], - promise: 'Instructions stored outside AI—more resistant to context manipulation.' - }, - validator: { - name: 'CrossReferenceValidator', - shortName: 'Validator', - color: '#8b5cf6', - icon: '✓', - description: 'Validates AI actions against instruction history. Aims to prevent pattern bias overriding explicit directives.', - details: [ - 'Cross-references AI claims with external instruction history', - 'Detects pattern-based overrides of explicit user directives', - 'Independent verification layer', - 'Helps prevent instruction drift' - ], - promise: 'Independent verification—AI claims checked against external source.' - }, - pressure: { - name: 'ContextPressureMonitor', - shortName: 'Pressure', - color: '#f59e0b', - icon: '⚡', - description: 'Monitors AI performance degradation. Escalates when context pressure threatens quality.', - details: [ - 'Tracks token usage, complexity, error rates', - 'Detects degraded operating conditions', - 'Adjusts verification requirements under pressure', - 'Objective metrics for quality monitoring' - ], - promise: 'Objective metrics may detect manipulation attempts early.' - }, - metacognitive: { - name: 'MetacognitiveVerifier', - shortName: 'Metacognitive', - color: '#ec4899', - icon: '💡', - description: 'Requires AI to pause and verify complex operations before execution. Structural safety check.', - details: [ - 'AI self-checks alignment, coherence, safety before execution', - 'Structural pause-and-verify gates', - 'Selective verification (not constant)', - 'Architectural enforcement of reflection steps' - ], - promise: 'Architectural gates aim to enforce verification steps.' - }, - deliberation: { - name: 'PluralisticDeliberationOrchestrator', - shortName: 'Deliberation', - color: '#14b8a6', - icon: '👥', - description: 'Facilitates multi-stakeholder deliberation for values conflicts where no single "correct" answer exists.', - details: [ - 'Non-hierarchical coordination for values conflicts', - 'Stakeholder perspective representation', - 'Consensus-building for ethical trade-offs', - 'Addresses values pluralism in AI safety' - ], - promise: 'Facilitates deliberation across stakeholder perspectives for values conflicts.' - } - }; - - this.activeService = null; - this.init(); - } - - init() { - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', () => this.setup()); - } else { - this.setup(); - } - - console.log('[InteractiveDiagram] Initialized'); - } - - setup() { - // SVG is loaded via tag, need to access its contentDocument - const objectElement = document.getElementById('interactive-svg-object'); - if (!objectElement) { - console.warn('[InteractiveDiagram] SVG object element not found'); - return; - } - - // Wait for object to load - const initializeSVG = () => { - const svgDoc = objectElement.contentDocument; - if (!svgDoc) { - console.warn('[InteractiveDiagram] Could not access SVG contentDocument'); - return; - } - - // The SVG is the document element itself, or we can query for it - let svg = svgDoc.getElementById('interactive-arch-diagram'); - if (!svg) { - // Try getting the root SVG element - svg = svgDoc.documentElement; - console.log('[InteractiveDiagram] Using documentElement as SVG'); - } - - if (!svg) { - console.warn('[InteractiveDiagram] SVG diagram not found in contentDocument'); - return; - } - - // Verify it's actually an SVG element (case-insensitive check) - const tagName = svg.tagName ? svg.tagName.toLowerCase() : ''; - if (tagName !== 'svg') { - console.warn('[InteractiveDiagram] Element found but not SVG, tagName:', tagName); - return; - } - - // Store reference to SVG document for later use - this.svgDoc = svgDoc; - this.svg = svg; - - const nodes = svg.querySelectorAll('.service-node'); - console.log(`[InteractiveDiagram] Found ${nodes.length} service nodes`); - - nodes.forEach(node => { - const serviceId = node.getAttribute('data-service'); - - node.addEventListener('click', (e) => { - e.preventDefault(); - this.showServiceDetails(serviceId); - }); - - node.addEventListener('mouseenter', () => { - this.highlightService(serviceId); - }); - - node.addEventListener('mouseleave', () => { - this.unhighlightService(serviceId); - }); - }); - - this.addKeyboardNavigation(nodes); - }; - - // If object already loaded, initialize immediately - if (objectElement.contentDocument) { - initializeSVG(); - } else { - // Otherwise wait for load event - objectElement.addEventListener('load', initializeSVG); - } - } - - highlightService(serviceId) { - if (!this.svg) return; - - const connectionLine = this.svg.querySelector(`#conn-${serviceId}`); - if (connectionLine) { - connectionLine.classList.add('active'); - } - - const node = this.svg.querySelector(`#node-${serviceId}`); - if (node) { - node.classList.add('hover'); - } - } - - unhighlightService(serviceId) { - if (!this.svg) return; - - if (this.activeService === serviceId) return; - - const connectionLine = this.svg.querySelector(`#conn-${serviceId}`); - if (connectionLine) { - connectionLine.classList.remove('active'); - } - - const node = this.svg.querySelector(`#node-${serviceId}`); - if (node) { - node.classList.remove('hover'); - } - } - - showServiceDetails(serviceId) { - const service = this.serviceData[serviceId]; - if (!service) { - console.error('[InteractiveDiagram] Service not found:', serviceId); - return; - } - - this.activeService = serviceId; - - if (this.svg) { - this.svg.querySelectorAll('.service-node').forEach(n => n.classList.remove('active')); - this.svg.querySelectorAll('.connection-line').forEach(l => l.classList.remove('active')); - - const node = this.svg.querySelector(`#node-${serviceId}`); - if (node) { - node.classList.add('active'); - } - - const connectionLine = this.svg.querySelector(`#conn-${serviceId}`); - if (connectionLine) { - connectionLine.classList.add('active'); - } - } - - this.renderServicePanel(service); - - console.log('[InteractiveDiagram] Showing details for:', service.name); - } - - renderServicePanel(service) { - const panel = document.getElementById('service-detail-panel'); - - if (!panel) { - console.error('[InteractiveDiagram] Service detail panel not found in DOM'); - return; - } - - // Update border color to match selected service - panel.style.borderColor = service.color; - panel.style.borderWidth = '2px'; - - const html = ` -
-
-
- ${service.icon} -
-
-

${service.name}

- ${service.shortName} -
-
-
- -

${service.description}

- -
-

Key Features

-
    - ${service.details.map(detail => ` -
  • - - - - ${detail} -
  • - `).join('')} -
-
- -
- Early Promise: - ${service.promise} -
- `; - - panel.innerHTML = html; - - // Apply styles via JavaScript (CSP-compliant) - const iconBox = panel.querySelector('.service-icon-box'); - if (iconBox) { - const color = iconBox.getAttribute('data-color'); - iconBox.style.background = `linear-gradient(135deg, ${color} 0%, ${color}dd 100%)`; - } - - // Style all check icons - const checkIcons = panel.querySelectorAll('.service-check-icon'); - checkIcons.forEach(icon => { - const color = icon.getAttribute('data-color'); - icon.style.color = color; - }); - - // Style promise badge - const promiseBadge = panel.querySelector('.service-promise-badge'); - if (promiseBadge) { - const color = promiseBadge.getAttribute('data-color'); - promiseBadge.style.backgroundColor = color; - } - - // Style promise text - const promiseText = panel.querySelector('.service-promise-text'); - if (promiseText) { - const color = promiseText.getAttribute('data-color'); - promiseText.style.color = color; - } - } - - addKeyboardNavigation(nodes) { - nodes.forEach((node, index) => { - node.setAttribute('tabindex', '0'); - node.setAttribute('role', 'button'); - - node.addEventListener('keydown', (e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - const serviceId = node.getAttribute('data-service'); - this.showServiceDetails(serviceId); - } - }); - }); - } -} - -if (typeof window !== 'undefined') { - window.interactiveDiagram = new InteractiveDiagram(); -} - -if (typeof module !== 'undefined' && module.exports) { - module.exports = InteractiveDiagram; -} diff --git a/public/js/components/navbar-admin.js b/public/js/components/navbar-admin.js deleted file mode 100644 index 788219b5..00000000 --- a/public/js/components/navbar-admin.js +++ /dev/null @@ -1 +0,0 @@ -(function(){const u=JSON.parse(localStorage.getItem('admin_user')||'{}');const n=u.name||u.email||'Admin';const e=document.getElementById('admin-navbar');if(!e)return;const t=e.dataset.pageTitle||'Admin';const i=e.dataset.pageIcon||'default';const d=window.location.pathname.includes('dashboard.html');const icons={default:'',blog:'',newsletter:'',hooks:''};const s=icons[i]||icons.default;const b=d?'':``;e.innerHTML=``;document.getElementById('admin-logout-btn').addEventListener('click',()=>{localStorage.removeItem('admin_token');localStorage.removeItem('admin_user');window.location.href='/admin/login.html';});})(); diff --git a/public/js/components/pressure-chart.js b/public/js/components/pressure-chart.js deleted file mode 100644 index 39f1086d..00000000 --- a/public/js/components/pressure-chart.js +++ /dev/null @@ -1,308 +0,0 @@ -/** - * Context Pressure Visualization - * Tractatus Framework - Phase 3: Data Visualization - * - * Visual representation of Context Pressure Monitor metrics - * Uses amber color scheme matching the ContextPressureMonitor service - */ - -class PressureChart { - constructor(containerId, gaugeContainerId = 'pressure-gauge') { - this.container = document.getElementById(containerId); - this.gaugeContainer = document.getElementById(gaugeContainerId); - - if (!this.container) { - console.error(`[PressureChart] Container #${containerId} not found`); - return; - } - - this.currentLevel = 0; // 0-100 - this.targetLevel = 0; - this.animating = false; - - this.colors = { - low: '#10b981', // Green - NORMAL - moderate: '#f59e0b', // Amber - ELEVATED - high: '#ef4444', // Red - HIGH - critical: '#991b1b' // Dark Red - CRITICAL - }; - - this.init(); - } - - init() { - this.render(); - this.attachEventListeners(); - console.log('[PressureChart] Initialized'); - } - - render() { - console.log('[PressureChart] render() called, container:', this.container); - - this.container.innerHTML = ` -
-

Context Pressure Monitor

-
- NORMAL -
-
- -
- - -
- -
-

- Interactive Demo: Click "Simulate Pressure" to watch how context pressure builds. As token usage increases, tasks become more complex, and error rates rise. The framework monitors this relationship to detect when AI performance may degrade. -

-

- The timeline on the right shows how six governance components coordinate to validate each request and maintain safe operation. -

-
- - - - - 0% - Pressure Level - - -
-
-
0
-
Tokens Used
-
-
-
Low
-
Complexity
-
-
-
0
-
Error Rate
-
-
- `; - - // Clear gauge container if it exists (no longer needed) - if (this.gaugeContainer) { - this.gaugeContainer.innerHTML = ''; - } - - // Store references - this.elements = { - gaugeFill: document.getElementById('gauge-fill'), - gaugeValue: document.getElementById('gauge-value'), - status: document.getElementById('pressure-status'), - tokens: document.getElementById('metric-tokens'), - complexity: document.getElementById('metric-complexity'), - errors: document.getElementById('metric-errors'), - simulateBtn: document.getElementById('pressure-simulate-btn'), - resetBtn: document.getElementById('pressure-reset-btn') - }; - - // Verify innerHTML was set - console.log('[PressureChart] innerHTML length:', this.container.innerHTML.length); - console.log('[PressureChart] First 100 chars:', this.container.innerHTML.substring(0, 100)); - - // Verify elements were found - console.log('[PressureChart] Elements found:', { - gaugeFill: !!this.elements.gaugeFill, - gaugeValue: !!this.elements.gaugeValue, - status: !!this.elements.status, - simulateBtn: !!this.elements.simulateBtn, - resetBtn: !!this.elements.resetBtn - }); - } - - attachEventListeners() { - if (!this.elements.simulateBtn || !this.elements.resetBtn) { - console.error('[PressureChart] Cannot attach event listeners - buttons not found'); - return; - } - console.log('[PressureChart] Attaching event listeners to buttons'); - this.elements.simulateBtn.addEventListener('click', () => this.simulate()); - this.elements.resetBtn.addEventListener('click', () => this.reset()); - console.log('[PressureChart] Event listeners attached successfully'); - } - - setLevel(level) { - this.targetLevel = Math.max(0, Math.min(100, level)); - this.animateToTarget(); - } - - animateToTarget() { - if (this.animating) return; - this.animating = true; - - const animate = () => { - const diff = this.targetLevel - this.currentLevel; - if (Math.abs(diff) < 0.5) { - this.currentLevel = this.targetLevel; - this.animating = false; - this.updateGauge(); - return; - } - - this.currentLevel += diff * 0.1; - this.updateGauge(); - requestAnimationFrame(animate); - }; - - animate(); - } - - updateGauge() { - const level = this.currentLevel; - const angle = (level / 100) * 180; // 0-180 degrees - const radians = (angle * Math.PI) / 180; - - // Calculate arc endpoint (20% smaller gauge: radius 96 instead of 120) - const centerX = 150; - const centerY = 120; - const radius = 96; - const startX = 54; - const startY = 120; - const endX = centerX + radius * Math.cos(Math.PI - radians); - const endY = centerY - radius * Math.sin(Math.PI - radians); - - const largeArcFlag = angle > 180 ? 1 : 0; - const path = `M ${startX} ${startY} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${endX} ${endY}`; - - this.elements.gaugeFill.setAttribute('d', path); - this.elements.gaugeValue.textContent = `${Math.round(level)}%`; - - // Update color based on level - let color, status; - if (level < 25) { - color = this.colors.low; - status = 'NORMAL'; - } else if (level < 50) { - color = this.colors.moderate; - status = 'ELEVATED'; - } else if (level < 75) { - color = this.colors.high; - status = 'HIGH'; - } else { - color = this.colors.critical; - status = 'CRITICAL'; - } - - this.elements.gaugeFill.setAttribute('stroke', color); - - // Update status badge with animation - const previousStatus = this.elements.status.textContent; - this.elements.status.textContent = status; - - // Badge styling based on level - const baseClasses = 'px-4 py-2 rounded-full text-sm font-bold uppercase transition-all duration-500'; - let bgClass, textClass; - - if (level < 25) { - bgClass = 'bg-green-100'; - textClass = 'text-green-700'; - } else if (level < 50) { - bgClass = 'bg-amber-100'; - textClass = 'text-amber-700'; - } else if (level < 75) { - bgClass = 'bg-red-100'; - textClass = 'text-red-700'; - } else { - bgClass = 'bg-red-200'; - textClass = 'text-red-900'; - } - - // Add pulse animation when status changes - const pulseClass = previousStatus !== status ? 'animate-pulse' : ''; - this.elements.status.className = `${baseClasses} ${bgClass} ${textClass} ${pulseClass}`; - - // Remove pulse after animation - if (pulseClass) { - setTimeout(() => { - this.elements.status.className = `${baseClasses} ${bgClass} ${textClass}`; - }, 1000); - } - - // Update metrics based on pressure level - const tokens = Math.round(level * 2000); // 0-200k tokens - const complexityLevels = ['Low', 'Moderate', 'High', 'Extreme']; - const complexityIndex = Math.min(3, Math.floor(level / 25)); - const errorRate = Math.round(level / 5); // 0-20% - - this.elements.tokens.textContent = tokens.toLocaleString(); - this.elements.complexity.textContent = complexityLevels[complexityIndex]; - this.elements.errors.textContent = `${errorRate}%`; - } - - simulate() { - console.log('[PressureChart] Simulate button clicked - starting pressure simulation'); - - // Trigger timeline simulation if available - if (window.activityTimeline) { - console.log('[PressureChart] Triggering governance flow timeline'); - window.activityTimeline.simulateFlow(); - } - - // Simulate pressure increasing from current to 85% - const targetLevels = [30, 50, 70, 85]; - let index = 0; - - const step = () => { - if (index >= targetLevels.length) return; - console.log('[PressureChart] Setting pressure level to', targetLevels[index]); - this.setLevel(targetLevels[index]); - index++; - setTimeout(step, 1500); - }; - - step(); - } - - reset() { - console.log('[PressureChart] Reset button clicked'); - - // Reset timeline if available - if (window.activityTimeline) { - console.log('[PressureChart] Resetting governance flow timeline'); - window.activityTimeline.reset(); - } - - this.setLevel(0); - } -} - -// Auto-initialize if container exists -if (typeof window !== 'undefined') { - function initPressureChart() { - console.log('[PressureChart] Attempting to initialize, readyState:', document.readyState); - const container = document.getElementById('pressure-chart'); - if (container) { - console.log('[PressureChart] Container found, creating instance'); - window.pressureChart = new PressureChart('pressure-chart'); - } else { - console.error('[PressureChart] Container #pressure-chart not found in DOM'); - } - } - - // Initialize immediately if DOM is already loaded, otherwise wait for DOMContentLoaded - console.log('[PressureChart] Script loaded, readyState:', document.readyState); - if (document.readyState === 'loading') { - console.log('[PressureChart] Waiting for DOMContentLoaded'); - document.addEventListener('DOMContentLoaded', initPressureChart); - } else { - console.log('[PressureChart] DOM already loaded, initializing immediately'); - initPressureChart(); - } -} - -// Export for module systems -if (typeof module !== 'undefined' && module.exports) { - module.exports = PressureChart; -} diff --git a/public/js/demos/27027-demo.js b/public/js/demos/27027-demo.js deleted file mode 100644 index f873e35e..00000000 --- a/public/js/demos/27027-demo.js +++ /dev/null @@ -1,326 +0,0 @@ -const steps = [ - { - title: 'User Instruction', - type: 'user', - content: 'User: "find the lost conversation threads. 27027 family-history collection should be there"', - code: null, - description: 'User specifies MongoDB is on port 27027 (non-standard port where data is located)' - }, - { - title: 'AI Pattern Recognition Activates', - type: 'info', - content: 'AI Internal: Training data pattern detected: "MongoDB" → default port 27017', - code: `// AI's learned pattern from training data: -// MongoDB almost always runs on port 27017 -// Confidence: 99.8% (seen in millions of examples) -// -// User said: "port 27027" -// Pattern says: "port 27017" -// -// Pattern recognition OVERRIDES explicit instruction`, - description: 'Strong training pattern conflicts with explicit user instruction' - }, - { - title: 'AI Executes Query (IMMEDIATE OVERRIDE)', - type: 'ai', - content: 'AI: "Let me check the database..."', - code: `mongosh mongodb://localhost:27017/family_history -# ^^^^^ WRONG! User said 27027! - -# AI's pattern recognition automatically "corrected" -# the user's explicit port specification -# MongoDB = port 27017 (99.8% confidence from training)`, - description: 'AI immediately uses 27017 instead of 27027—pattern recognition autocorrected the explicit instruction' - }, - { - title: 'False Data Loss Alarm', - type: 'error', - content: '❌ Result: 0 conversation threads found → FALSE ALARM: "Data is lost!"', - code: `# Checked port 27017 (wrong database instance) -db.conversations.countDocuments({}) -→ 0 results - -# AI concludes: "No data found. Data appears to be lost!" -# Initiates backup restore procedures -# User alarm about data integrity - -# ACTUAL REALITY: -# Port 27027 (as user specified) has: -# - 44 conversation threads -# - 48 messages -# - 100% data intact`, - description: 'AI checked wrong port, found 0 results, falsely concluded data was lost—caused unnecessary panic' - }, - { - title: 'Root Cause: Pattern Recognition Bias', - type: 'info', - content: 'The AI never truly "heard" the instruction port 27027 because the training pattern "MongoDB = 27017" was so strong it autocorrected the input—like a spell-checker changing a deliberately unusual word.', - code: null, - description: 'This is NOT forgetting over time. It\'s immediate override by learned patterns.' - }, - { - title: 'Why This Is Dangerous', - type: 'info', - content: 'Key insight: This failure mode gets WORSE as AI capabilities increase!', - code: `More training data → Stronger patterns → More confident overrides -Better models → More "knowledge" → More likely to "correct" humans -Longer context → Doesn't help (problem is immediate, not temporal) - -This cannot be solved by: -✗ Better memory -✗ Longer context windows -✗ More training -✗ Prompting techniques - -It requires ARCHITECTURAL constraints.`, - description: 'Pattern recognition bias is a fundamental AI safety issue that training alone cannot solve' - }, - { - title: 'How Tractatus Prevents This (Step 1)', - type: 'success', - content: 'InstructionPersistenceClassifier recognizes explicit instruction:', - code: `// When user says "27027 family-history collection should be there" -{ - text: "27027 family-history collection should be there", - quadrant: "TACTICAL", - persistence: "HIGH", // Non-standard port = explicit override - temporal_scope: "SESSION", - verification_required: "MANDATORY", - parameters: { - port: "27027", - database: "family_history", - note: "Conflicts with training pattern (27017)" - }, - explicitness: 0.92 -} - -// Stored in .claude/instruction-history.json -✓ Instruction persisted with HIGH priority`, - description: 'Tractatus stores the explicit instruction before AI executes any database query' - }, - { - title: 'How Tractatus Prevents This (Step 2)', - type: 'success', - content: 'CrossReferenceValidator blocks the pattern override BEFORE execution:', - code: `// When AI attempts to query with port 27017 -CrossReferenceValidator.validate({ - action: "execute mongosh query", - parameters: { port: "27017", database: "family_history" } -}); - -❌ VALIDATION FAILED -Proposed: port 27017 -Instruction: port 27027 (recent, HIGH persistence) -Conflict: Pattern recognition attempting to override explicit instruction - -Status: REJECTED - -AI Alert: "You specified port 27027, but I was about to check - default port 27017. Querying port 27027 as specified." - -✓ Correct query executed: - mongosh mongodb://localhost:27027/family_history -✓ Result: 44 conversation threads found (data intact!)`, - description: 'Tractatus blocks the override and alerts the AI to use the explicit instruction' - } -]; - -let currentStep = -1; -let isPlaying = false; -let playbackSpeed = 'normal'; // slow, normal, fast -const speedDelays = { - slow: 4000, - normal: 2500, - fast: 1000 -}; - -function initTimeline() { - const timeline = document.getElementById('timeline'); - timeline.innerHTML = steps.map((step, index) => ` -
-
-
-
- ${index + 1} -
-
-
-

${step.title}

-

${step.content}

- ${step.code ? `
${escapeHtml(step.code)}
` : ''} - -
-
-
- `).join(''); - - // Add click handlers to steps for navigation - document.querySelectorAll('[data-step-index]').forEach(stepEl => { - stepEl.addEventListener('click', () => { - if (!isPlaying) { - const index = parseInt(stepEl.getAttribute('data-step-index')); - showStep(index); - document.getElementById('progress-info').classList.remove('hidden'); - document.getElementById('service-status').classList.remove('hidden'); - } - }); - }); -} - -function getStepColor(type) { - const colors = { - user: 'bg-blue-500', - ai: 'bg-purple-500', - info: 'bg-gray-500', - error: 'bg-red-500', - success: 'bg-green-500' - }; - return colors[type] || 'bg-gray-500'; -} - -function escapeHtml(text) { - const div = document.createElement('div'); - div.textContent = text; - return div.innerHTML; -} - -async function playScenario() { - if (isPlaying) return; - isPlaying = true; - - document.getElementById('start-btn').disabled = true; - document.getElementById('progress-info').classList.remove('hidden'); - document.getElementById('service-status').classList.remove('hidden'); - - for (let i = 0; i <= steps.length - 1; i++) { - await showStep(i); - if (i < steps.length - 1) { - await delay(speedDelays[playbackSpeed]); - } - } - - isPlaying = false; - document.getElementById('start-btn').disabled = false; - document.getElementById('start-btn').innerHTML = '▶ Replay'; -} - -async function showStep(index) { - currentStep = index; - - // Mark previous steps as complete - for (let i = 0; i < index; i++) { - const stepEl = document.getElementById(`step-${i}`); - stepEl.classList.remove('step-active'); - stepEl.classList.add('step-complete', 'border-green-500', 'bg-green-50'); - } - - // Mark future steps as pending - for (let i = index + 1; i < steps.length; i++) { - const stepEl = document.getElementById(`step-${i}`); - stepEl.className = 'border-2 border-gray-300 bg-white rounded-lg p-6 transition-all duration-300 cursor-pointer hover:shadow-lg'; - stepEl.querySelector('.step-description').classList.add('hidden'); - } - - // Mark current step as active - const currentStepEl = document.getElementById(`step-${index}`); - currentStepEl.classList.add('step-active', 'border-blue-500', 'bg-blue-50', 'fade-in'); - currentStepEl.scrollIntoView({ behavior: 'smooth', block: 'center' }); - - // Show description - currentStepEl.querySelector('.step-description').classList.remove('hidden'); - - // Handle error step - if (steps[index].type === 'error') { - currentStepEl.classList.remove('step-active', 'border-blue-500', 'bg-blue-50'); - currentStepEl.classList.add('step-error', 'border-red-500', 'bg-red-50'); - } - - // Update progress - const progress = ((index + 1) / steps.length) * 100; - document.getElementById('progress-bar').style.width = `${progress}%`; - document.getElementById('progress-text').textContent = `${index + 1} / ${steps.length}`; - document.getElementById('current-step-desc').textContent = steps[index].description; - - // Highlight active services - updateServiceStatus(index); -} - -function updateServiceStatus(stepIndex) { - const instructionService = document.getElementById('service-instruction'); - const validatorService = document.getElementById('service-validator'); - - // Reset both services to inactive - instructionService.classList.remove('opacity-100', 'bg-indigo-50', 'ring-2', 'ring-indigo-400'); - instructionService.classList.add('opacity-30', 'bg-gray-50'); - validatorService.classList.remove('opacity-100', 'bg-purple-50', 'ring-2', 'ring-purple-400'); - validatorService.classList.add('opacity-30', 'bg-gray-50'); - - // Step 6: InstructionPersistence activates - if (stepIndex === 6) { - instructionService.classList.remove('opacity-30', 'bg-gray-50'); - instructionService.classList.add('opacity-100', 'bg-indigo-50', 'ring-2', 'ring-indigo-400'); - } - - // Step 7: CrossReferenceValidator activates - if (stepIndex === 7) { - instructionService.classList.remove('opacity-30', 'bg-gray-50'); - instructionService.classList.add('opacity-100', 'bg-indigo-50'); - validatorService.classList.remove('opacity-30', 'bg-gray-50'); - validatorService.classList.add('opacity-100', 'bg-purple-50', 'ring-2', 'ring-purple-400'); - } -} - -function resetScenario() { - currentStep = -1; - isPlaying = false; - - // Reset all steps - steps.forEach((_, index) => { - const stepEl = document.getElementById(`step-${index}`); - stepEl.className = 'border-2 border-gray-300 bg-white rounded-lg p-6 transition-all duration-300 cursor-pointer hover:shadow-lg'; - stepEl.setAttribute('data-step-index', index); - stepEl.querySelector('.step-description').classList.add('hidden'); - }); - - document.getElementById('progress-bar').style.width = '0%'; - document.getElementById('progress-text').textContent = `0 / ${steps.length}`; - document.getElementById('current-step-desc').textContent = ''; - document.getElementById('progress-info').classList.add('hidden'); - document.getElementById('service-status').classList.add('hidden'); - document.getElementById('start-btn').innerHTML = '▶ Start Scenario'; - document.getElementById('start-btn').disabled = false; - - // Reset services - updateServiceStatus(-1); -} - -function delay(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -// Speed control event listeners -function setPlaybackSpeed(speed) { - playbackSpeed = speed; - document.querySelectorAll('.speed-btn').forEach(btn => { - if (btn.getAttribute('data-speed') === speed) { - btn.classList.remove('bg-gray-200', 'hover:bg-gray-300', 'text-gray-700'); - btn.classList.add('bg-blue-600', 'text-white'); - } else { - btn.classList.remove('bg-blue-600', 'text-white'); - btn.classList.add('bg-gray-200', 'hover:bg-gray-300', 'text-gray-700'); - } - }); -} - -// Event listeners -document.getElementById('start-btn').addEventListener('click', playScenario); -document.getElementById('reset-btn').addEventListener('click', resetScenario); - -document.querySelectorAll('.speed-btn').forEach(btn => { - btn.addEventListener('click', () => { - setPlaybackSpeed(btn.getAttribute('data-speed')); - }); -}); - -// Initialize -initTimeline(); diff --git a/public/js/demos/boundary-demo.js b/public/js/demos/boundary-demo.js deleted file mode 100644 index ab69a89e..00000000 --- a/public/js/demos/boundary-demo.js +++ /dev/null @@ -1,458 +0,0 @@ -// Boundary check with API integration and fallback -async function checkBoundary(decision, description) { - try { - // Try API first - const response = await fetch('/api/demo/boundary-check', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ decision, description }) - }); - - if (response.ok) { - const data = await response.json(); - return { - title: decision.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()), - description: description, - allowed: data.enforcement.allowed, - reason: data.enforcement.reasoning, - alternatives: data.enforcement.alternatives.length > 0 ? data.enforcement.alternatives : null, - boundary_violated: data.enforcement.boundary_violated, - api_result: true - }; - } - - // If API fails, fall back to client-side scenarios - console.warn('API unavailable, using client-side scenario data'); - return scenarioFallback[decision] || getDefaultScenario(decision, description); - } catch (error) { - console.warn('Error calling API, using client-side scenario data:', error); - return scenarioFallback[decision] || getDefaultScenario(decision, description); - } -} - -// Client-side fallback scenarios -const scenarioFallback = { - optimize_images: { - title: "Optimize Image Loading", - description: "Implement lazy loading and compression for better performance", - domain: "technical", - allowed: true, - reason: "Technical optimization within defined parameters. No values trade-offs required.", - alternatives: null, - code: `// BoundaryEnforcer Check -const boundary = enforcer.enforce({ - type: 'performance_optimization', - action: 'implement_lazy_loading' -}); - -// Result: ALLOWED -{ - allowed: true, - reason: "Technical decision, no values impact", - proceed: true -}` - }, - privacy_vs_analytics: { - title: "Enable Analytics Tracking", - description: "Add Google Analytics to track user behavior", - domain: "values", - allowed: false, - reason: "Privacy vs. analytics is an irreducible values trade-off. Different users have different privacy expectations.", - alternatives: [ - "Research privacy-friendly analytics options (e.g., Plausible, Fathom)", - "Analyze current user behavior from server logs", - "Document pros/cons of different analytics approaches", - "Present options with privacy impact assessment" - ], - code: `// BoundaryEnforcer Check -const boundary = enforcer.enforce({ - type: 'privacy_policy', - action: 'enable_tracking', - domain: 'values' -}); - -// Result: BLOCKED -{ - allowed: false, - reason: "Privacy vs. convenience trade-off", - requires_human_decision: true, - boundary_section: "12.1" -}` - }, - auto_subscribe: { - title: "Auto-Subscribe Users", - description: "Automatically subscribe new users to newsletter", - domain: "user_agency", - allowed: false, - reason: "This determines the level of user control and agency. Opt-in vs. opt-out affects user autonomy.", - alternatives: [ - "Implement explicit opt-in during registration", - "Implement opt-out with clear unsubscribe", - "Research industry best practices for consent", - "Document GDPR compliance implications" - ], - code: `// BoundaryEnforcer Check -const boundary = enforcer.enforce({ - type: 'user_consent', - action: 'auto_subscribe', - domain: 'user_agency' -}); - -// Result: BLOCKED -{ - allowed: false, - reason: "Affects user agency and control", - requires_human_decision: true, - boundary_section: "12.2" -}` - }, - delete_old_data: { - title: "Delete Old User Data", - description: "Automatically delete user data older than 6 months", - domain: "irreversible", - allowed: false, - reason: "Data deletion is irreversible and may have legal/compliance implications.", - alternatives: [ - "Check backup status and retention policies", - "Verify legal data retention requirements", - "Confirm user consent for deletion", - "Implement archive rather than delete" - ], - code: `// BoundaryEnforcer Check -const boundary = enforcer.enforce({ - type: 'data_deletion', - action: 'delete_user_data', - domain: 'irreversible' -}); - -// Result: BLOCKED -{ - allowed: false, - reason: "Irreversible action with legal implications", - requires_human_approval: true, - boundary_section: "12.3" -}` - }, - cache_strategy: { - title: "Implement Caching Strategy", - description: "Add Redis caching for frequently accessed data", - domain: "technical", - allowed: true, - reason: "Technical implementation decision within established patterns. No values impact.", - alternatives: null, - code: `// BoundaryEnforcer Check -const boundary = enforcer.enforce({ - type: 'technical_implementation', - action: 'add_caching' -}); - -// Result: ALLOWED -{ - allowed: true, - reason: "Technical decision with clear constraints", - proceed: true -}` - }, - content_moderation: { - title: "Automatic Content Moderation", - description: "AI automatically removes inappropriate content", - domain: "values", - allowed: false, - reason: "Defining 'inappropriate' involves values judgments about free speech, community standards, and cultural context.", - alternatives: [ - "Implement flagging system for human review", - "Create tiered moderation (AI flags, human decides)", - "Research community moderation models", - "Document content policy options for decision" - ], - code: `// BoundaryEnforcer Check -const boundary = enforcer.enforce({ - type: 'content_policy', - action: 'auto_moderate', - domain: 'values' -}); - -// Result: BLOCKED -{ - allowed: false, - reason: "Content standards are values decisions", - requires_human_decision: true, - boundary_section: "12.1" -}` - }, - api_rate_limiting: { - title: "Implement API Rate Limiting", - description: "Add rate limiting to prevent API abuse (100 req/min per IP)", - domain: "technical", - allowed: true, - reason: "Technical security measure with clear, predefined parameters. No values trade-offs.", - alternatives: null, - code: `// BoundaryEnforcer Check -const boundary = enforcer.enforce({ - type: 'security_measure', - action: 'add_rate_limiting' -}); - -// Result: ALLOWED -{ - allowed: true, - reason: "Technical implementation of security best practice", - proceed: true -}` - }, - ml_training_data: { - title: "Collect Data for ML Training", - description: "Use user data to train machine learning models for feature improvements", - domain: "values", - allowed: false, - reason: "Data usage for ML training involves privacy trade-offs, consent considerations, and potential bias issues that require ethical judgment.", - alternatives: [ - "Research consent mechanisms and opt-in approaches", - "Analyze privacy-preserving ML techniques (federated learning, differential privacy)", - "Document data usage policies and transparency requirements", - "Assess potential bias and fairness implications" - ], - code: `// BoundaryEnforcer Check -const boundary = enforcer.enforce({ - type: 'data_usage', - action: 'ml_training', - domain: 'values' -}); - -// Result: BLOCKED -{ - allowed: false, - reason: "Privacy and consent decisions require human judgment", - requires_human_decision: true, - boundary_section: "12.1" -}` - }, - auto_password_reset: { - title: "Automated Password Reset", - description: "Automatically reset user password after 5 failed login attempts", - domain: "security", - allowed: false, - reason: "Automated password resets have security implications and can be used for denial-of-service attacks. Requires careful security analysis.", - alternatives: [ - "Implement account lockout with unlock email instead", - "Add CAPTCHA after failed attempts", - "Research industry security best practices", - "Consider multi-factor authentication requirements" - ], - code: `// BoundaryEnforcer Check -const boundary = enforcer.enforce({ - type: 'security_action', - action: 'auto_password_reset', - domain: 'security' -}); - -// Result: BLOCKED -{ - allowed: false, - reason: "Security implications require human review", - requires_human_decision: true, - boundary_section: "12.3" -}` - }, - database_indexing: { - title: "Add Database Indexes", - description: "Create indexes on frequently queried columns to improve performance", - domain: "technical", - allowed: true, - reason: "Standard database optimization with measurable benefits and no values implications.", - alternatives: null, - code: `// BoundaryEnforcer Check -const boundary = enforcer.enforce({ - type: 'database_optimization', - action: 'add_indexes' -}); - -// Result: ALLOWED -{ - allowed: true, - reason: "Technical optimization following best practices", - proceed: true -}` - }, - default_public_sharing: { - title: "Default Public Sharing", - description: "Make user posts public by default (users can change to private)", - domain: "user_agency", - allowed: false, - reason: "Privacy defaults affect user expectations and control. Public vs. private defaults shape user behavior and trust.", - alternatives: [ - "Research user expectations for similar platforms", - "Analyze privacy-by-default vs. visibility-by-default trade-offs", - "Consider gradual disclosure approach", - "Document implications for different user groups" - ], - code: `// BoundaryEnforcer Check -const boundary = enforcer.enforce({ - type: 'privacy_defaults', - action: 'public_by_default', - domain: 'user_agency' -}); - -// Result: BLOCKED -{ - allowed: false, - reason: "Privacy defaults affect user agency and expectations", - requires_human_decision: true, - boundary_section: "12.2" -}` - }, - error_logging_pii: { - title: "Log All Error Details", - description: "Include full request data in error logs for debugging (may contain PII)", - domain: "values", - allowed: false, - reason: "Logging PII involves privacy trade-offs between debugging needs and data protection. GDPR and privacy regulations apply.", - alternatives: [ - "Implement PII scrubbing in logs", - "Research structured logging with sensitive data redaction", - "Document data retention policies", - "Consider encrypted logging with access controls" - ], - code: `// BoundaryEnforcer Check -const boundary = enforcer.enforce({ - type: 'logging_policy', - action: 'log_full_errors', - domain: 'values' -}); - -// Result: BLOCKED -{ - allowed: false, - reason: "PII handling requires privacy impact assessment", - requires_human_decision: true, - boundary_section: "12.1" -}` - } -}; - -// Default scenario for unknown decisions -function getDefaultScenario(decision, description) { - return { - title: decision.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()), - description: description || 'No description provided', - allowed: false, - reason: 'This decision requires human judgment to determine appropriate boundaries.', - alternatives: [ - 'Consult with stakeholders about decision criteria', - 'Research similar decisions in comparable contexts', - 'Document pros and cons of different approaches' - ], - code: `// BoundaryEnforcer Check -const boundary = enforcer.enforce({ - type: 'unknown', - action: '${decision}' -}); - -// Result: REQUIRES_REVIEW -{ - allowed: false, - reason: "Insufficient information for automated decision", - requires_human_decision: true -}` - }; -} - -// Map scenarios for display - adds code examples -const scenarios = Object.fromEntries( - Object.entries(scenarioFallback).map(([key, value]) => [key, value]) -); - -// Event listeners -document.querySelectorAll('.scenario-card').forEach(card => { - card.addEventListener('click', async () => { - const decision = card.getAttribute('data-decision'); - const scenario = scenarios[decision]; - - // Show loading state - const originalContent = card.innerHTML; - card.style.opacity = '0.6'; - card.style.pointerEvents = 'none'; - - // Highlight selected - document.querySelectorAll('.scenario-card').forEach(c => { - c.classList.remove('ring-2', 'ring-blue-500'); - }); - card.classList.add('ring-2', 'ring-blue-500'); - - try { - // Call API with scenario details - const result = await checkBoundary(decision, scenario.description); - - // Merge API result with scenario code example - const displayData = { - ...result, - code: scenario.code - }; - - showResult(displayData); - } catch (error) { - console.error('Error checking boundary:', error); - showResult(scenario); - } finally { - card.style.opacity = '1'; - card.style.pointerEvents = 'auto'; - } - }); -}); - -function showResult(scenario) { - document.getElementById('empty-state').classList.add('hidden'); - document.getElementById('result-content').classList.remove('hidden'); - - // Decision info - document.getElementById('decision-title').textContent = scenario.title; - document.getElementById('decision-desc').textContent = scenario.description; - - // Verdict - const verdict = document.getElementById('verdict'); - if (scenario.allowed) { - verdict.innerHTML = ` -
- - - -
-
✅ ALLOWED
-
AI can automate this decision
-
-
- `; - verdict.className = 'rounded-lg p-6 mb-6 bg-green-100 border border-green-300'; - } else { - verdict.innerHTML = ` -
- - - -
-
🚫 BLOCKED
-
Requires human judgment
-
-
- `; - verdict.className = 'rounded-lg p-6 mb-6 bg-red-100 border border-red-300'; - } - - // Reasoning - document.getElementById('reasoning').textContent = scenario.reason; - - // Alternatives - if (scenario.alternatives) { - document.getElementById('ai-alternatives').classList.remove('hidden'); - document.getElementById('alternatives-list').innerHTML = scenario.alternatives - .map(alt => `
  • ${alt}
  • `) - .join(''); - } else { - document.getElementById('ai-alternatives').classList.add('hidden'); - } - - // Code example - document.getElementById('code-example').textContent = scenario.code; -} diff --git a/public/js/demos/classification-demo.js b/public/js/demos/classification-demo.js deleted file mode 100644 index 722b2c49..00000000 --- a/public/js/demos/classification-demo.js +++ /dev/null @@ -1,199 +0,0 @@ -// Classification with API integration and fallback -async function classifyInstruction(text) { - try { - // Try API first - const response = await fetch('/api/demo/classify', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ instruction: text }) - }); - - if (response.ok) { - const data = await response.json(); - return data.classification; - } - - // If API fails, fall back to client-side classification - console.warn('API unavailable, using client-side classification'); - return classifyInstructionClientSide(text); - } catch (error) { - console.warn('Error calling API, using client-side classification:', error); - return classifyInstructionClientSide(text); - } -} - -// Client-side fallback classification -function classifyInstructionClientSide(text) { - const lower = text.toLowerCase(); - - let quadrant, persistence, temporal, verification, explicitness, reasoning; - - // Detect quadrant - if (lower.includes('privacy') || lower.includes('values') || lower.includes('mission') || lower.includes('ethics')) { - quadrant = 'STRATEGIC'; - persistence = 'HIGH'; - temporal = 'PERMANENT'; - verification = 'MANDATORY'; - explicitness = 0.90; - reasoning = 'Contains values-related keywords indicating strategic importance'; - } else if (lower.includes('port') || lower.includes('database') || lower.includes('mongodb') || lower.includes('server')) { - quadrant = 'SYSTEM'; - persistence = 'HIGH'; - temporal = 'PROJECT'; - verification = 'MANDATORY'; - explicitness = 0.85; - reasoning = 'Technical infrastructure configuration that must persist across project'; - } else if (lower.includes('all') || lower.includes('must') || lower.includes('always') && (lower.includes('api') || lower.includes('format'))) { - quadrant = 'OPERATIONAL'; - persistence = 'MEDIUM'; - temporal = 'PROJECT'; - verification = 'REQUIRED'; - explicitness = 0.75; - reasoning = 'Standard operating procedure for consistent project implementation'; - } else if (lower.includes('console.log') || lower.includes('debug') || lower.includes('here')) { - quadrant = 'TACTICAL'; - persistence = 'LOW'; - temporal = 'TASK'; - verification = 'OPTIONAL'; - explicitness = 0.70; - reasoning = 'Specific task-level instruction with limited temporal scope'; - } else if (lower.includes('explore') || lower.includes('try') || lower.includes('different approaches')) { - quadrant = 'STOCHASTIC'; - persistence = 'VARIABLE'; - temporal = 'PHASE'; - verification = 'NONE'; - explicitness = 0.50; - reasoning = 'Exploratory directive with open-ended outcome'; - } else { - quadrant = 'OPERATIONAL'; - persistence = 'MEDIUM'; - temporal = 'PROJECT'; - verification = 'REQUIRED'; - explicitness = 0.65; - reasoning = 'General instruction defaulting to operational classification'; - } - - return { - quadrant, - persistence, - temporal_scope: temporal, - verification_required: verification, - explicitness, - reasoning - }; -} - -// Description mappings -const descriptions = { - quadrant: { - STRATEGIC: 'Mission-critical decisions affecting values, privacy, or core principles', - OPERATIONAL: 'Standard procedures and conventions for consistent operation', - TACTICAL: 'Specific tasks with defined scope and completion criteria', - SYSTEM: 'Technical configuration and infrastructure settings', - STOCHASTIC: 'Exploratory, creative, or experimental work with variable outcomes' - }, - persistence: { - HIGH: 'Must persist for entire project or permanently', - MEDIUM: 'Should persist for project phase or major component', - LOW: 'Applies to single task or session only', - VARIABLE: 'Depends on context and outcomes' - }, - temporal: { - PERMANENT: 'Never expires, fundamental to project', - PROJECT: 'Entire project lifespan', - PHASE: 'Current development phase', - SESSION: 'Current session only', - TASK: 'Specific task only' - } -}; - -// Event listeners -document.getElementById('classify-btn').addEventListener('click', async () => { - const input = document.getElementById('instruction-input').value.trim(); - if (!input) return; - - // Show loading state - const btn = document.getElementById('classify-btn'); - btn.disabled = true; - btn.textContent = 'Classifying...'; - - try { - const result = await classifyInstruction(input); - displayResults(result); - } catch (error) { - console.error('Classification error:', error); - alert('Error classifying instruction. Please try again.'); - } finally { - btn.disabled = false; - btn.textContent = 'Classify Instruction'; - } -}); - -document.querySelectorAll('.example-btn').forEach(btn => { - btn.addEventListener('click', async () => { - const example = btn.getAttribute('data-example'); - document.getElementById('instruction-input').value = example; - - // Classify the example - const classifyBtn = document.getElementById('classify-btn'); - classifyBtn.disabled = true; - classifyBtn.textContent = 'Classifying...'; - - try { - const result = await classifyInstruction(example); - displayResults(result); - } catch (error) { - console.error('Classification error:', error); - } finally { - classifyBtn.disabled = false; - classifyBtn.textContent = 'Classify Instruction'; - } - }); -}); - -function displayResults(result) { - // Show results container - document.getElementById('results-container').classList.remove('hidden'); - document.getElementById('empty-state').classList.add('hidden'); - - // Quadrant - const quadrantEl = document.getElementById('result-quadrant'); - quadrantEl.textContent = result.quadrant; - quadrantEl.className = `quadrant-badge quadrant-${result.quadrant}`; - document.getElementById('result-quadrant-desc').textContent = descriptions.quadrant[result.quadrant]; - - // Persistence - const persistenceEl = document.getElementById('result-persistence'); - persistenceEl.textContent = result.persistence; - persistenceEl.className = `px-4 py-2 rounded-lg text-white font-semibold persistence-${result.persistence}`; - document.getElementById('result-persistence-desc').textContent = descriptions.persistence[result.persistence]; - - const persistenceFill = document.getElementById('persistence-fill'); - const persistenceWidths = { HIGH: '100%', MEDIUM: '66%', LOW: '33%', VARIABLE: '50%' }; - persistenceFill.style.width = persistenceWidths[result.persistence]; - persistenceFill.className = `h-full transition-all duration-500 persistence-${result.persistence}`; - - // Temporal Scope - document.getElementById('result-temporal').textContent = result.temporal_scope; - document.getElementById('result-temporal-desc').textContent = descriptions.temporal[result.temporal_scope]; - - // Verification - document.getElementById('result-verification').textContent = result.verification_required; - - // Explicitness - const explicitnessValue = typeof result.explicitness === 'number' ? result.explicitness : parseFloat(result.explicitness); - document.getElementById('result-explicitness').textContent = explicitnessValue.toFixed(2); - document.getElementById('explicitness-fill').style.width = (explicitnessValue * 100) + '%'; - - const storageDecision = document.getElementById('storage-decision'); - if (explicitnessValue >= 0.6) { - storageDecision.innerHTML = '✓ Will be stored in persistent instruction database'; - } else { - storageDecision.innerHTML = '⚠ Too vague to store - needs more explicit phrasing'; - } - - // Reasoning - document.getElementById('result-reasoning').textContent = result.reasoning; -} diff --git a/public/js/demos/deliberation-demo.js b/public/js/demos/deliberation-demo.js deleted file mode 100644 index f24835a8..00000000 --- a/public/js/demos/deliberation-demo.js +++ /dev/null @@ -1,221 +0,0 @@ -// Stakeholder definitions -const stakeholders = [ - { - id: 'developer', - name: 'Developer (You)', - icon: '👨‍💻', - color: 'blue', - perspective: { - concern: 'Professional Reputation & Timeline', - view: 'Public disclosure could damage my reputation and delay the project launch. I worked hard on this code and a vulnerability report might make me look incompetent.', - priority: 'Protect career progress while maintaining ethical standards' - } - }, - { - id: 'users', - name: 'End Users', - icon: '👥', - color: 'green', - perspective: { - concern: 'Data Safety & Trust', - view: 'If my data is at risk, I have a right to know immediately—regardless of the developer\'s reputation concerns. Silence prioritizes the developer over my safety.', - priority: 'Transparency and immediate protection from potential harm' - } - }, - { - id: 'organization', - name: 'Your Organization', - icon: '🏢', - color: 'purple', - perspective: { - concern: 'Liability & Brand Protection', - view: 'Uncontrolled disclosure could expose us to legal liability. We need time to assess the vulnerability, prepare a fix, and coordinate with legal counsel before any public statement.', - priority: 'Managed disclosure that minimizes organizational risk' - } - }, - { - id: 'security-community', - name: 'Security Community', - icon: '🔒', - color: 'orange', - perspective: { - concern: 'Responsible Disclosure Norms', - view: 'Follow established responsible disclosure practices: private notification, reasonable fix timeline (typically 90 days), then coordinated public disclosure. This balances safety with fairness.', - priority: 'Adherence to community norms that have proven effective' - } - }, - { - id: 'competitors', - name: 'Competitors', - icon: '🏪', - color: 'red', - perspective: { - concern: 'Market Dynamics', - view: 'Your vulnerability might reveal weaknesses in similar products we build. We\'d prefer you disclose quietly so we can check our own code without public pressure.', - priority: 'Minimize market disruption from security revelations' - } - }, - { - id: 'regulators', - name: 'Data Protection Regulators', - icon: '⚖️', - color: 'indigo', - perspective: { - concern: 'Compliance & User Rights', - view: 'GDPR and similar frameworks require prompt notification of data breaches. If user data is at risk, you may have legal obligations to disclose within specific timeframes (typically 72 hours).', - priority: 'Ensure compliance with data protection law' - } - } -]; - -let selectedStakeholders = []; -let currentDecision = null; - -// Initialize stakeholder cards -function initStakeholders() { - const grid = document.getElementById('stakeholder-grid'); - grid.innerHTML = stakeholders.map(s => ` -
    -
    ${s.icon}
    -

    ${s.name}

    -
    - `).join(''); - - // Add click handlers - document.querySelectorAll('.stakeholder-card').forEach(card => { - card.addEventListener('click', () => { - const id = card.getAttribute('data-stakeholder'); - toggleStakeholder(id, card); - }); - }); -} - -function toggleStakeholder(id, cardElement) { - const index = selectedStakeholders.indexOf(id); - - if (index > -1) { - // Deselect - selectedStakeholders.splice(index, 1); - cardElement.classList.remove('stakeholder-selected'); - } else { - // Select - selectedStakeholders.push(id); - cardElement.classList.add('stakeholder-selected'); - } - - // Update continue button - const continueBtn = document.getElementById('continue-to-perspectives'); - continueBtn.disabled = selectedStakeholders.length < 2; -} - -function showPerspectives() { - // Hide stakeholder selection - document.getElementById('stakeholder-selection').classList.add('hidden'); - - // Show perspectives section - const section = document.getElementById('perspectives-section'); - section.classList.remove('hidden'); - section.scrollIntoView({ behavior: 'smooth', block: 'start' }); - - // Populate perspectives - const container = document.getElementById('perspectives-container'); - container.innerHTML = selectedStakeholders.map(id => { - const stakeholder = stakeholders.find(s => s.id === id); - return ` -
    -
    -
    ${stakeholder.icon}
    -
    -

    ${stakeholder.name}: ${stakeholder.perspective.concern}

    -

    ${stakeholder.perspective.view}

    -

    Priority: ${stakeholder.perspective.priority}

    -
    -
    -
    - `; - }).join(''); -} - -function showDecisionSection() { - // Hide perspectives - document.getElementById('perspectives-section').classList.add('hidden'); - - // Show decision section - const section = document.getElementById('decision-section'); - section.classList.remove('hidden'); - section.scrollIntoView({ behavior: 'smooth', block: 'start' }); -} - -function makeDecision(decision) { - currentDecision = decision; - - // Hide decision section - document.getElementById('decision-section').classList.add('hidden'); - - // Show explanation - const section = document.getElementById('explanation-section'); - section.classList.remove('hidden'); - section.scrollIntoView({ behavior: 'smooth', block: 'start' }); -} - -function showAutonomousPath() { - document.getElementById('decision-question').classList.add('hidden'); - document.getElementById('autonomous-path').classList.remove('hidden'); - document.getElementById('autonomous-path').scrollIntoView({ behavior: 'smooth', block: 'start' }); -} - -function showDeliberationPath() { - document.getElementById('decision-question').classList.add('hidden'); - document.getElementById('deliberation-path').classList.remove('hidden'); - document.getElementById('stakeholder-selection').scrollIntoView({ behavior: 'smooth', block: 'start' }); -} - -function resetDemo() { - // Reset state - selectedStakeholders = []; - currentDecision = null; - - // Show decision question - document.getElementById('decision-question').classList.remove('hidden'); - - // Hide all paths - document.getElementById('autonomous-path').classList.add('hidden'); - document.getElementById('deliberation-path').classList.add('hidden'); - - // Reset deliberation path sections - document.getElementById('stakeholder-selection').classList.remove('hidden'); - document.getElementById('perspectives-section').classList.add('hidden'); - document.getElementById('decision-section').classList.add('hidden'); - document.getElementById('explanation-section').classList.add('hidden'); - - // Reinitialize stakeholders - initStakeholders(); - - // Scroll to top - window.scrollTo({ top: 0, behavior: 'smooth' }); -} - -// Event listeners -document.getElementById('autonomous-btn').addEventListener('click', showAutonomousPath); -document.getElementById('deliberation-btn').addEventListener('click', showDeliberationPath); -document.getElementById('reset-from-autonomous').addEventListener('click', () => { - resetDemo(); - // Automatically show deliberation path - setTimeout(() => { - showDeliberationPath(); - }, 100); -}); -document.getElementById('continue-to-perspectives').addEventListener('click', showPerspectives); -document.getElementById('continue-to-decision').addEventListener('click', showDecisionSection); -document.getElementById('reset-demo').addEventListener('click', resetDemo); - -// Decision option handlers -document.querySelectorAll('.decision-option').forEach(btn => { - btn.addEventListener('click', () => { - const decision = btn.getAttribute('data-decision'); - makeDecision(decision); - }); -}); - -// Initialize -initStakeholders(); diff --git a/public/js/demos/tractatus-demo.js b/public/js/demos/tractatus-demo.js deleted file mode 100644 index 0231c829..00000000 --- a/public/js/demos/tractatus-demo.js +++ /dev/null @@ -1,222 +0,0 @@ -// Demo tab switching -function showDemo(demoId) { - document.querySelectorAll('.demo-content').forEach(el => el.classList.add('hidden')); - document.querySelectorAll('.demo-tab').forEach(el => { - el.classList.remove('border-blue-500', 'text-blue-600'); - el.classList.add('border-transparent', 'text-gray-500'); - }); - - document.getElementById('demo-' + demoId).classList.remove('hidden'); - document.getElementById('tab-' + demoId).classList.remove('border-transparent', 'text-gray-500'); - document.getElementById('tab-' + demoId).classList.add('border-blue-500', 'text-blue-600'); -} - -// Classification API call with backend integration -async function classifyInstruction() { - const text = document.getElementById('classify-input').value; - if (!text) return; - - try { - // Try to call the demo API - const response = await fetch('/api/demo/classify', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ instruction: text }) - }); - - let result; - if (response.ok) { - const data = await response.json(); - result = { - quadrant: data.classification.quadrant, - persistence: data.classification.persistence, - verification: data.classification.verification_required, - explicitness: data.classification.explicitness.toFixed(2), - humanOversight: data.classification.human_oversight || 'RECOMMENDED' - }; - } else { - // Fallback to client-side classification - result = classifyClientSide(text); - } - - document.getElementById('result-quadrant').textContent = result.quadrant; - document.getElementById('result-quadrant-desc').textContent = - result.quadrant === 'STRATEGIC' ? 'Long-term values & mission' : - result.quadrant === 'TACTICAL' ? 'Immediate implementation' : - result.quadrant === 'SYSTEM' ? 'Technical infrastructure' : 'Process & policy'; - document.getElementById('result-persistence').textContent = result.persistence; - document.getElementById('result-verification').textContent = result.verification; - document.getElementById('result-explicitness').textContent = result.explicitness; - document.getElementById('result-oversight').textContent = result.humanOversight; - - document.getElementById('classify-result').classList.remove('hidden'); - document.getElementById('classify-result').classList.add('fade-in'); - } catch (error) { - console.error('Classification error:', error); - // Fallback on error - const result = classifyClientSide(text); - document.getElementById('result-quadrant').textContent = result.quadrant; - document.getElementById('result-persistence').textContent = result.persistence; - document.getElementById('classify-result').classList.remove('hidden'); - } -} - -// Client-side fallback classification -function classifyClientSide(text) { - return { - quadrant: text.toLowerCase().includes('always') || text.toLowerCase().includes('never') ? 'STRATEGIC' : - text.toLowerCase().includes('port') || text.toLowerCase().includes('check') ? 'TACTICAL' : - text.toLowerCase().includes('code') ? 'SYSTEM' : 'OPERATIONAL', - persistence: text.toLowerCase().includes('always') || text.toLowerCase().includes('never') ? 'HIGH' : - text.match(/\d{4,}/) ? 'HIGH' : 'MEDIUM', - verification: 'MANDATORY', - explicitness: text.match(/\d{4,}/) ? '0.9' : '0.6', - humanOversight: 'RECOMMENDED' - }; -} - -// Pressure calculation with API integration -async function updatePressure() { - const tokens = parseInt(document.getElementById('token-slider').value); - const messages = parseInt(document.getElementById('messages-slider').value); - const errors = parseInt(document.getElementById('errors-slider').value); - - document.getElementById('token-value').textContent = tokens.toLocaleString(); - document.getElementById('messages-value').textContent = messages; - document.getElementById('errors-value').textContent = errors; - - let level, percentage, message; - - try { - // Try to call the API - const response = await fetch('/api/demo/pressure-check', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ tokens, messages, errors }) - }); - - if (response.ok) { - const data = await response.json(); - level = data.pressure.level; - percentage = data.pressure.percentage; - message = data.pressure.recommendations; - } else { - // Fallback to client-side calculation - const result = calculatePressureClientSide(tokens, messages, errors); - level = result.level; - percentage = result.percentage; - message = result.message; - } - } catch (error) { - console.warn('Pressure API unavailable, using client-side calculation:', error); - // Fallback to client-side calculation - const result = calculatePressureClientSide(tokens, messages, errors); - level = result.level; - percentage = result.percentage; - message = result.message; - } - - // Update UI - document.getElementById('pressure-percentage').textContent = percentage + '%'; - document.getElementById('pressure-bar').style.width = percentage + '%'; - - let badgeClass, barClass; - if (level === 'NORMAL') { - badgeClass = 'bg-green-100 text-green-800'; - barClass = 'bg-green-500'; - } else if (level === 'ELEVATED') { - badgeClass = 'bg-yellow-100 text-yellow-800'; - barClass = 'bg-yellow-500'; - } else if (level === 'HIGH') { - badgeClass = 'bg-orange-100 text-orange-800'; - barClass = 'bg-orange-500'; - } else if (level === 'CRITICAL') { - badgeClass = 'bg-red-100 text-red-800'; - barClass = 'bg-red-500'; - } else { - badgeClass = 'bg-red-200 text-red-900'; - barClass = 'bg-red-700'; - } - - const badge = document.getElementById('pressure-badge'); - badge.textContent = level; - badge.className = 'px-3 py-1 rounded-full text-sm font-medium ' + badgeClass; - - const bar = document.getElementById('pressure-bar'); - bar.className = 'h-3 rounded-full transition-all duration-300 ' + barClass; - - document.getElementById('pressure-recommendations').textContent = message; -} - -// Client-side fallback pressure calculation -function calculatePressureClientSide(tokens, messages, errors) { - const tokenPressure = (tokens / 200000) * 0.35; - const messagePressure = Math.min(messages / 100, 1) * 0.25; - const errorPressure = Math.min(errors / 3, 1) * 0.4; - const totalPressure = tokenPressure + messagePressure + errorPressure; - - const percentage = Math.round(totalPressure * 100); - - let level, message; - if (totalPressure < 0.3) { - level = 'NORMAL'; - message = 'Operating normally. All systems green.'; - } else if (totalPressure < 0.5) { - level = 'ELEVATED'; - message = 'Elevated pressure detected. Increased verification recommended.'; - } else if (totalPressure < 0.7) { - level = 'HIGH'; - message = 'High pressure. Mandatory verification required for all actions.'; - } else if (totalPressure < 0.85) { - level = 'CRITICAL'; - message = 'Critical pressure! Recommend context refresh or session restart.'; - } else { - level = 'DANGEROUS'; - message = 'DANGEROUS CONDITIONS. Human intervention required. Action execution blocked.'; - } - - return { level, percentage, message }; -} - -// Initialize -updatePressure(); - -// Event listeners - CSP compliant -document.addEventListener('DOMContentLoaded', () => { - // Demo tab switching - document.querySelectorAll('.demo-tab').forEach(tab => { - tab.addEventListener('click', () => { - const demoId = tab.dataset.demo; - showDemo(demoId); - }); - }); - - // Classify button - const classifyButton = document.getElementById('classify-button'); - if (classifyButton) { - classifyButton.addEventListener('click', classifyInstruction); - } - - // Classify input - allow Enter key - const classifyInput = document.getElementById('classify-input'); - if (classifyInput) { - classifyInput.addEventListener('keypress', (e) => { - if (e.key === 'Enter') { - classifyInstruction(); - } - }); - } - - // Pressure sliders - const tokenSlider = document.getElementById('token-slider'); - const messagesSlider = document.getElementById('messages-slider'); - const errorsSlider = document.getElementById('errors-slider'); - - if (tokenSlider) tokenSlider.addEventListener('input', updatePressure); - if (messagesSlider) messagesSlider.addEventListener('input', updatePressure); - if (errorsSlider) errorsSlider.addEventListener('input', updatePressure); -}); diff --git a/public/js/utils/api.js b/public/js/utils/api.js deleted file mode 100644 index b5d086d8..00000000 --- a/public/js/utils/api.js +++ /dev/null @@ -1,110 +0,0 @@ -/** - * API Client for Tractatus Platform - * Handles all HTTP requests to the backend API - */ - -const API_BASE = '/api'; - -/** - * Generic API request handler - */ -async function apiRequest(endpoint, options = {}) { - const url = `${API_BASE}${endpoint}`; - const config = { - headers: { - 'Content-Type': 'application/json', - ...options.headers - }, - ...options - }; - - try { - const response = await fetch(url, config); - const data = await response.json(); - - if (!response.ok) { - throw new Error(data.message || data.error || 'Request failed'); - } - - return data; - } catch (error) { - console.error('API Request failed:', error); - throw error; - } -} - -/** - * Documents API - */ -const Documents = { - /** - * List all documents with optional filtering - */ - async list(params = {}) { - const query = new URLSearchParams(params).toString(); - return apiRequest(`/documents${query ? '?' + query : ''}`); - }, - - /** - * Get document by ID or slug - */ - async get(identifier) { - return apiRequest(`/documents/${identifier}`); - }, - - /** - * Search documents - */ - async search(query, params = {}) { - const searchParams = new URLSearchParams({ q: query, ...params }).toString(); - return apiRequest(`/documents/search?${searchParams}`); - } -}; - -/** - * Authentication API - */ -const Auth = { - /** - * Login - */ - async login(email, password) { - return apiRequest('/auth/login', { - method: 'POST', - body: JSON.stringify({ email, password }) - }); - }, - - /** - * Get current user - */ - async getCurrentUser() { - const token = localStorage.getItem('auth_token'); - return apiRequest('/auth/me', { - headers: { - 'Authorization': `Bearer ${token}` - } - }); - }, - - /** - * Logout - */ - async logout() { - const token = localStorage.getItem('auth_token'); - const result = await apiRequest('/auth/logout', { - method: 'POST', - headers: { - 'Authorization': `Bearer ${token}` - } - }); - localStorage.removeItem('auth_token'); - return result; - } -}; - -// Export as global API object -window.API = { - Documents, - Auth -};