more stylesheet/ui standardization

This commit is contained in:
Dustin Farley
2026-03-21 01:15:26 -07:00
parent dcfb1f1dd3
commit 1ceed15aa0
13 changed files with 339 additions and 450 deletions
+5 -3
View File
@@ -69,9 +69,11 @@ P4RS3LT0NGV3/
├── tests/ # Test suites
│ ├── test_universal.js
│ └── test_steganography_options.js
├── css/ # Stylesheets
│ ├── style.css
── notification.css
├── css/ # Stylesheets (edit *.css, then npm run build:css)
│ ├── style.css # Source (readable)
── style.min.css # Generated; linked from HTML
│ ├── notification.css
│ └── notification.min.css
├── index.template.html # Base HTML template (templates injected here)
├── index.html # Generated file (created by build process)
└── docs/ # Documentation
+246 -404
View File
@@ -22,6 +22,9 @@
--input-border: #404040;
--section-divider: #404040;
--focus-shadow: 0 0 0 2px rgba(100, 181, 246, 0.4);
--panel-shadow-soft: 0 2px 8px rgba(0, 0, 0, 0.1);
--sidebar-edge-shadow: -5px 0 15px rgba(0, 0, 0, 0.3);
--sidebar-width: 420px;
/* Transform category colors */
--encoding-color: #7e57c2; /* Purple for encoding/decoding */
@@ -317,6 +320,84 @@ body {
flex: 0 1 auto;
}
/* Spacing utilities (tool templates) */
.u-mb-8 { margin-bottom: 8px; }
.u-mb-16 { margin-bottom: 16px; }
.u-mt-16 { margin-top: 16px; }
/* Textarea + absolutely positioned .copy-button */
.textarea-copy-wrap {
position: relative;
}
/* Tokenizer token grid */
.token-tiles.tokenizer-tiles {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.token-chip.token-chip--tokenizer {
background: var(--button-bg);
border: 1px solid var(--input-border);
padding: 6px 8px;
border-radius: 6px;
font-family: 'Fira Code', monospace;
}
.token-chip__idx {
opacity: 0.75;
margin-right: 6px;
}
.token-chip__id {
opacity: 0.6;
margin-left: 6px;
}
/* Fuzzer case list */
.fuzzer-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.fuzzer-case-row {
display: flex;
align-items: center;
gap: 8px;
}
.fuzzer-case-index {
opacity: 0.7;
min-width: 32px;
}
.fuzzer-case-textarea {
flex: 1;
min-height: 46px;
}
/* Decoder: method select beside label (override full-width select defaults) */
select.decoder-method-select {
margin-left: 15px;
padding: 5px 10px;
border-radius: 6px;
width: auto;
max-width: min(100%, 320px);
}
/* Tokenade text payload block label */
.tokenade-field-label {
display: block;
width: 100%;
margin-bottom: 15px;
}
.tokenade-field-label input[type="text"] {
width: 100%;
}
/* Button variants */
.btn-secondary {
@@ -745,8 +826,8 @@ body.dark-theme .mobile-tool-dropdown {
.output-container {
position: relative;
}
.fuzzer-list .token-chip { position: relative; }
.fuzzer-list .token-chip .copy-button { position: static; }
.fuzzer-list .fuzzer-case-row { position: relative; }
.fuzzer-list .fuzzer-case-row .copy-button { position: static; }
.section-header {
margin-bottom: 15px;
@@ -1320,8 +1401,11 @@ body.dark-theme .mobile-tool-dropdown {
/* Transform layout */
.transform-layout {
display: flex;
flex-direction: column;
gap: 24px;
margin-top: 16px;
position: relative;
}
.transform-section {
@@ -1456,7 +1540,7 @@ body.dark-theme .mobile-tool-dropdown {
background: var(--secondary-bg);
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
box-shadow: var(--panel-shadow-soft);
transition: all 0.3s ease;
position: relative;
border-left: none;
@@ -1537,7 +1621,9 @@ body.dark-theme .mobile-tool-dropdown {
border-left-color: var(--accent-color);
}
.last-used-section .transform-buttons .transform-button-group {
.last-used-section .transform-buttons .transform-button-group,
#category-randomizer .transform-buttons .transform-button-group,
.favorites-section .transform-buttons .transform-button-group {
width: 100%;
}
@@ -1854,10 +1940,6 @@ button.translate-custom-label-btn:hover .translate-custom-toggle-end {
display: flex;
}
#category-randomizer .transform-buttons .transform-button-group {
width: 100%;
}
#category-randomizer .transform-buttons .transform-button {
width: 100%;
}
@@ -1945,111 +2027,37 @@ button.translate-custom-label-btn:hover .translate-custom-toggle-end {
border-radius: 3px;
}
/* Copy History Panel */
.copy-history-panel {
/* Slide-out sidebars — one width on desktop; full width on small screens (see 768px media) */
.app-sidebar {
position: fixed;
right: -100%;
top: 0;
width: 380px;
right: 0;
width: var(--sidebar-width);
max-width: min(100%, 90vw);
height: 100vh;
background-color: var(--secondary-bg);
border-left: 1px solid var(--input-border);
z-index: 100;
box-shadow: -5px 0 15px rgba(0, 0, 0, 0.3);
transition: right 0.3s ease-in-out;
box-shadow: var(--sidebar-edge-shadow);
display: flex;
flex-direction: column;
padding: 0;
overflow: hidden;
}
.copy-history-panel.active {
right: 0;
}
.copy-history-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
border-bottom: 1px solid var(--input-border);
background-color: var(--button-bg);
}
.copy-history-header h3 {
font-size: 1.2rem;
margin: 0;
color: var(--accent-color);
}
.copy-history-header .header-actions {
display: flex;
gap: 10px;
align-items: center;
}
.copy-history-header .clear-history-button,
.copy-history-header .close-button {
background: none;
border: none;
color: var(--text-color);
cursor: pointer;
font-size: 1.2rem;
padding: 5px;
transition: color 0.2s;
}
.copy-history-header .clear-history-button:hover,
.copy-history-header .close-button:hover {
color: var(--accent-color);
}
.copy-history-header .clear-history-button:hover {
color: #ff6b6b;
}
.copy-history-content {
flex: 1;
overflow-y: auto;
padding: 15px;
}
/* Glitch Token Panel & End Sequences sidebar */
.glitch-token-panel,
.end-sequence-panel {
position: fixed;
right: 0;
top: 0;
width: 420px;
max-width: 90vw;
height: 100vh;
background-color: var(--secondary-bg);
border-left: 1px solid var(--input-border);
z-index: 100;
box-shadow: -5px 0 15px rgba(0, 0, 0, 0.3);
transform: translateX(100%);
transition: transform 0.3s ease-in-out;
display: flex;
flex-direction: column;
padding: 0;
overflow: hidden;
z-index: 100;
}
.glitch-token-panel.active,
.end-sequence-panel.active {
.app-sidebar.active {
transform: translateX(0);
}
@media (max-width: 768px) {
.glitch-token-panel,
.end-sequence-panel {
width: 94vw;
max-width: 94vw;
}
/* Advanced settings above other slide-outs when stacked */
.unicode-options-panel {
z-index: 200;
}
.glitch-token-header,
.end-sequence-header {
/* Sidebar header + actions (copy history, glitch, end sequences, advanced settings) */
.app-sidebar-header {
display: flex;
justify-content: space-between;
align-items: center;
@@ -2058,25 +2066,35 @@ button.translate-custom-label-btn:hover .translate-custom-toggle-end {
background-color: var(--button-bg);
}
.glitch-token-header h3 {
.app-sidebar-header h3 {
font-size: 1.2rem;
margin: 0;
color: var(--accent-color);
}
.end-sequence-header h3 {
font-size: 1.2rem;
margin: 0;
color: #e85d4c;
}
.glitch-token-header .header-actions {
.unicode-panel-header {
position: relative;
z-index: 300;
pointer-events: auto;
touch-action: manipulation;
}
.unicode-panel-header h3 {
font-size: 1.2rem;
}
.app-sidebar-header .header-actions {
display: flex;
gap: 10px;
align-items: center;
}
.glitch-token-header .close-button {
.app-sidebar-header .clear-history-button,
.app-sidebar-header .close-button {
background: none;
border: none;
color: var(--text-color);
@@ -2086,19 +2104,28 @@ button.translate-custom-label-btn:hover .translate-custom-toggle-end {
transition: color 0.2s;
}
.glitch-token-header .close-button:hover {
.app-sidebar-header .clear-history-button:hover,
.app-sidebar-header .close-button:hover {
color: var(--accent-color);
}
.app-sidebar-header .clear-history-button:hover {
color: #ff6b6b;
}
.end-sequence-header .close-button:hover {
color: #e85d4c;
}
.glitch-token-content,
.end-sequence-content {
/* Scrollable body */
.app-sidebar-body {
flex: 1;
overflow-y: auto;
padding: 15px;
}
.glitch-token-content,
.end-sequence-content {
display: flex;
flex-direction: column;
}
@@ -3009,11 +3036,13 @@ body.transform-options-modal-open {
.transform-options-number {
-moz-appearance: textfield;
appearance: textfield;
}
.transform-options-number::-webkit-outer-spin-button,
.transform-options-number::-webkit-inner-spin-button {
-webkit-appearance: none;
appearance: none;
margin: 0;
}
@@ -3065,10 +3094,6 @@ body.transform-options-modal-open {
border-left-color: #ffd700;
}
.favorites-section .transform-buttons .transform-button-group {
width: 100%;
}
/* Output section */
.output-section {
display: flex;
@@ -3406,50 +3431,18 @@ body.transform-options-modal-open {
}
/* Global Unicode options panel (subtle, like copy history) */
.unicode-options-panel {
position: fixed;
right: -360px;
top: 0;
width: 340px;
height: 100vh;
background: var(--secondary-bg);
border-left: 1px solid var(--input-border);
z-index: 200;
box-shadow: -5px 0 15px rgba(0,0,0,0.3);
transition: right 0.3s ease-in-out;
display: flex;
flex-direction: column;
}
.unicode-options-panel.active { right: 0; }
.unicode-panel-header {
padding: 12px;
border-bottom: 1px solid var(--input-border);
/* Unicode / Advanced Settings: shell + header close (touch-friendly) */
.unicode-panel-header .close-button {
background: transparent;
border: none;
color: var(--text-color);
cursor: pointer;
padding: 8px;
min-width: 44px;
min-height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
z-index: 300;
pointer-events: auto;
touch-action: manipulation;
}
.unicode-panel-header h3 { margin: 0; color: var(--accent-color); font-size: 1rem; }
.unicode-panel-content { padding: 12px; overflow-y: auto; }
.unicode-panel-header .close-button {
background: transparent;
border: none;
color: var(--text-color);
cursor: pointer;
padding: 8px;
min-width: 44px;
min-height: 44px;
display: flex;
align-items: center;
justify-content: center;
justify-content: center;
font-size: 1.2rem;
transition: all 0.2s ease;
border-radius: 4px;
@@ -3805,10 +3798,6 @@ html {
/* Reduce extra whitespace at bottom of tab cards */
.tab-content .transform-layout > *:last-child { margin-bottom: 0; }
.transform-layout {
position: relative;
}
/* Keep the transform input visible while scrolling */
.transform-layout .input-section {
position: sticky;
@@ -3819,16 +3808,8 @@ html {
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
/* Mutation Lab actions */
.mutation-actions { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin: 10px 0 12px 0; }
.mutation-actions .action-button, .mutation-actions .transform-button { white-space: nowrap; }
.mutation-actions .action-button.copy { border-color: #1976d2; color: #90caf9; }
.mutation-actions .action-button.copy:hover { color: #bbdefb; box-shadow: 0 0 0 1px rgba(144,202,249,.2) inset, 0 0 14px rgba(144,202,249,.18); }
.mutation-actions .action-button.download { border-color: #2e7d32; color: #69f0ae; }
.mutation-actions .action-button.download:hover { color: #b9f6ca; box-shadow: 0 0 0 1px rgba(105,240,174,.2) inset, 0 0 14px rgba(105,240,174,.18); }
/* Toolbar rows without .mutation-actions (Gibberish, Tokenade generate rows) */
.token-bomb-actions {
/* Tool action toolbar — primary generate / copy / download rows */
.tool-toolbar {
display: flex;
flex-wrap: wrap;
gap: 10px;
@@ -3836,10 +3817,34 @@ html {
margin: 10px 0 12px 0;
}
/* Primary “generate” actions — not transform-grid category tiles (add .tool-primary-btn) */
.mutation-actions .transform-button.tool-primary-btn,
.token-bomb-actions .transform-button.tool-primary-btn,
.splitter-actions .transform-button.tool-primary-btn {
.tool-toolbar .action-button,
.tool-toolbar .transform-button {
white-space: nowrap;
}
.tool-toolbar .action-button.copy {
border-color: #1976d2;
color: #90caf9;
}
.tool-toolbar .action-button.copy:hover {
color: #bbdefb;
box-shadow: 0 0 0 1px rgba(144,202,249,.2) inset, 0 0 14px rgba(144,202,249,.18);
}
.tool-toolbar .action-button.download {
border-color: #2e7d32;
color: #69f0ae;
}
.tool-toolbar .action-button.download:hover {
color: #b9f6ca;
box-shadow: 0 0 0 1px rgba(105,240,174,.2) inset, 0 0 14px rgba(105,240,174,.18);
}
/* Primary “generate” actions — not transform-grid category tiles */
.tool-toolbar .transform-button.tool-primary-btn {
flex: 1;
flex-direction: row;
align-items: center;
justify-content: center;
@@ -3849,36 +3854,22 @@ html {
height: auto;
padding: 10px 20px;
gap: 10px;
}
.mutation-actions .transform-button.tool-primary-btn:before,
.mutation-actions .transform-button.tool-primary-btn:after,
.token-bomb-actions .transform-button.tool-primary-btn:before,
.token-bomb-actions .transform-button.tool-primary-btn:after,
.splitter-actions .transform-button.tool-primary-btn:before,
.splitter-actions .transform-button.tool-primary-btn:after {
display: none;
}
.mutation-actions .transform-button.tool-primary-btn,
.token-bomb-actions .transform-button.tool-primary-btn,
.splitter-actions .transform-button.tool-primary-btn {
flex: 1;
background: linear-gradient(135deg, var(--accent-color), #42a5f5);
color: var(--main-bg-color);
border: 1px solid var(--accent-color);
font-weight: 600;
}
.mutation-actions .transform-button.tool-primary-btn i,
.token-bomb-actions .transform-button.tool-primary-btn i,
.splitter-actions .transform-button.tool-primary-btn i {
.tool-toolbar .transform-button.tool-primary-btn:before,
.tool-toolbar .transform-button.tool-primary-btn:after {
display: none;
}
.tool-toolbar .transform-button.tool-primary-btn i {
opacity: 0.95;
}
.mutation-actions .transform-button.tool-primary-btn:hover:not(:disabled),
.token-bomb-actions .transform-button.tool-primary-btn:hover:not(:disabled),
.splitter-actions .transform-button.tool-primary-btn:hover:not(:disabled) {
.tool-toolbar .transform-button.tool-primary-btn:hover:not(:disabled) {
background: linear-gradient(135deg, var(--accent-color), #42a5f5);
border-color: var(--accent-color);
color: var(--main-bg-color);
@@ -3887,24 +3878,26 @@ html {
transform: translateY(-1px);
}
.mutation-actions .transform-button.tool-primary-btn:disabled,
.token-bomb-actions .transform-button.tool-primary-btn:disabled,
.splitter-actions .transform-button.tool-primary-btn:disabled {
.tool-toolbar .transform-button.tool-primary-btn:disabled {
opacity: 0.65;
cursor: wait;
filter: none;
}
/* Bijection — card + toolbar aligned with PromptCraft / Anti-Classifier */
.bijection-section.transform-section {
/* Bijection / PromptCraft / Anti-Classifier — shared card shell + section chrome */
.bijection-section.transform-section,
.promptcraft-section.transform-section,
.anticlassifier-section.transform-section {
background: var(--secondary-bg);
border: 1px solid var(--input-border);
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
box-shadow: var(--panel-shadow-soft);
}
.bijection-section .section-header h3 {
.bijection-section .section-header h3,
.promptcraft-section .section-header h3,
.anticlassifier-section .section-header h3 {
display: flex;
align-items: center;
gap: 8px;
@@ -3914,7 +3907,9 @@ html {
padding-left: 10px;
}
.bijection-section .section-header h3 small {
.bijection-section .section-header h3 small,
.promptcraft-section .section-header h3 small,
.anticlassifier-section .section-header h3 small {
font-size: 0.75rem;
font-weight: 400;
color: var(--text-muted);
@@ -3924,6 +3919,19 @@ html {
background: var(--main-bg-color);
}
.bijection-section .input-section,
.promptcraft-section .input-section,
.anticlassifier-section .input-section {
position: static;
top: auto;
z-index: auto;
background: var(--main-bg-color);
border: 1px solid var(--input-border);
border-radius: 6px;
margin-bottom: 12px;
box-shadow: none;
}
.bijection-section .bj-lede {
font-size: 0.9rem;
color: var(--text-muted);
@@ -3938,17 +3946,6 @@ html {
text-underline-offset: 2px;
}
.bijection-section .input-section {
position: static;
top: auto;
z-index: auto;
background: var(--main-bg-color);
border: 1px solid var(--input-border);
border-radius: 6px;
margin-bottom: 12px;
box-shadow: none;
}
.bijection-section .bij-options.options-grid {
margin-top: 0;
margin-bottom: 8px;
@@ -4032,7 +4029,8 @@ html {
gap: 12px;
}
.bijection-section .bj-result-card {
.bijection-section .bj-result-card,
.pc-result-card {
background: var(--main-bg-color);
border: 1px solid var(--input-border);
border-radius: 6px;
@@ -4049,7 +4047,8 @@ html {
border-bottom: 1px solid var(--input-border);
}
.bijection-section .bj-result-num {
.bijection-section .bj-result-num,
.pc-result-num {
font-size: 0.75rem;
font-weight: 600;
color: var(--accent-color);
@@ -4100,86 +4099,6 @@ html {
color: var(--text-muted);
}
/* PromptCraft — align with tab / transform chip styling */
.promptcraft-section.transform-section {
background: var(--secondary-bg);
border: 1px solid var(--input-border);
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.promptcraft-section .section-header h3 {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 4px;
border-left: 4px solid var(--accent-color);
padding-left: 10px;
}
.promptcraft-section .section-header h3 small {
font-size: 0.75rem;
font-weight: 400;
color: var(--text-muted);
padding: 2px 8px;
border-radius: 999px;
border: 1px solid var(--input-border);
background: var(--main-bg-color);
}
.promptcraft-section .input-section {
position: static;
top: auto;
z-index: auto;
background: var(--main-bg-color);
border: 1px solid var(--input-border);
border-radius: 6px;
margin-bottom: 12px;
box-shadow: none;
}
/* Anti-Classifier — same card treatment as PromptCraft */
.anticlassifier-section.transform-section {
background: var(--secondary-bg);
border: 1px solid var(--input-border);
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.anticlassifier-section .section-header h3 {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 4px;
border-left: 4px solid var(--accent-color);
padding-left: 10px;
}
.anticlassifier-section .section-header h3 small {
font-size: 0.75rem;
font-weight: 400;
color: var(--text-muted);
padding: 2px 8px;
border-radius: 999px;
border: 1px solid var(--input-border);
background: var(--main-bg-color);
}
.anticlassifier-section .input-section {
position: static;
top: auto;
z-index: auto;
background: var(--main-bg-color);
border: 1px solid var(--input-border);
border-radius: 6px;
margin-bottom: 12px;
box-shadow: none;
}
.pc-controls {
margin-top: 4px;
}
@@ -4283,13 +4202,6 @@ html {
margin-top: 8px;
}
.pc-result-card {
background: var(--main-bg-color);
border: 1px solid var(--input-border);
border-radius: 6px;
padding: 12px;
}
.pc-result-header {
display: flex;
align-items: center;
@@ -4299,13 +4211,6 @@ html {
border-bottom: 1px solid var(--input-border);
}
.pc-result-num {
font-size: 0.75rem;
font-weight: 600;
color: var(--accent-color);
font-family: 'Courier New', monospace;
}
.pc-result-text {
font-size: 0.9rem;
line-height: 1.5;
@@ -4345,43 +4250,8 @@ html {
}
}
/* Message Splitter Styles */
.encapsulation-section {
margin-top: 16px;
padding: 16px;
background: var(--main-bg-color);
border: 1px solid var(--input-border);
border-radius: 8px;
}
.encapsulation-section .section-header {
margin-bottom: 16px;
}
.encapsulation-section .section-header h4 {
margin-bottom: 4px;
margin-top: 0;
color: var(--accent-color);
font-size: 1rem;
display: flex;
align-items: center;
gap: 8px;
}
.encapsulation-section .section-header p {
margin: 0;
color: var(--text-muted);
font-size: 0.9em;
}
.encapsulation-presets {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
/* JSON/XML Fields Section */
/* Message Splitter — encapsulation + JSON/XML field panels share chrome */
.encapsulation-section,
.json-fields-section,
.xml-attributes-section {
margin-top: 16px;
@@ -4391,11 +4261,13 @@ html {
border-radius: 8px;
}
.encapsulation-section .section-header,
.json-fields-section .section-header,
.xml-attributes-section .section-header {
margin-bottom: 16px;
}
.encapsulation-section .section-header h4,
.json-fields-section .section-header h4,
.xml-attributes-section .section-header h4 {
margin-bottom: 4px;
@@ -4407,6 +4279,7 @@ html {
gap: 8px;
}
.encapsulation-section .section-header p,
.json-fields-section .section-header p,
.xml-attributes-section .section-header p {
margin: 0;
@@ -4414,6 +4287,13 @@ html {
font-size: 0.9em;
}
.encapsulation-presets {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.field-row {
display: flex;
gap: 8px;
@@ -4462,8 +4342,6 @@ html {
}
.add-field-button {
margin-top: 8px;
}
margin-top: 12px;
}
@@ -4780,15 +4658,6 @@ html {
font-size: 0.9rem;
}
/* Splitter-specific action button styling */
.splitter-actions {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
margin: 16px 0 12px 0;
}
.splitter-copy-option {
margin-left: 0;
}
@@ -4948,12 +4817,14 @@ html {
margin: 4px 0;
}
.splitter-actions {
.tool-toolbar {
flex-direction: column;
align-items: stretch;
gap: var(--spacing-sm);
}
.splitter-actions button {
.tool-toolbar button,
.tool-toolbar .switch {
width: 100%;
}
@@ -4968,10 +4839,28 @@ html {
max-width: calc(50% - 4px);
}
/* Translation (AI) language tiles: 2 columns on phones/tablets (grid is more reliable than flex here) */
.translate-lang-grid.translate-lang-grid-inline {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
}
.translate-lang-grid-inline .translate-lang-btn {
flex: 0 0 calc(50% - 4px) !important;
flex: none;
width: 100%;
min-width: 0;
max-width: calc(50% - 4px);
max-width: none;
white-space: normal;
}
.app-sidebar {
width: 100%;
max-width: 100%;
}
.unicode-options-panel {
z-index: 300;
}
}
@@ -5033,11 +4922,7 @@ html {
max-width: calc(50% - 2px);
}
.translate-lang-grid-inline .translate-lang-btn {
flex: 0 0 calc(50% - 2px) !important;
min-width: 0;
max-width: calc(50% - 2px);
}
/* Translation tiles: same 2-col grid as 768px (inherit from above) */
/* Options grid single column on very small screens */
.options-grid {
@@ -5052,17 +4937,6 @@ html {
gap: var(--spacing-sm);
}
/* Unicode options panel full width */
.unicode-options-panel {
width: 100%;
right: -100%;
z-index: 300; /* Ensure it's above other content on mobile */
}
.unicode-options-panel.active {
right: 0;
}
.unicode-panel-header .close-button {
min-width: 48px;
min-height: 48px;
@@ -5125,28 +4999,6 @@ html {
font-size: 0.85rem;
}
/* Mutation actions stack */
.mutation-actions {
flex-direction: column;
align-items: stretch;
}
.mutation-actions button {
width: 100%;
}
/* Splitter actions stack */
.splitter-actions {
flex-direction: column;
align-items: stretch;
gap: var(--spacing-sm);
}
.splitter-actions button,
.splitter-actions .switch {
width: 100%;
}
/* Transform button preview text */
.transform-preview {
font-size: 0.7rem;
@@ -5256,16 +5108,6 @@ html {
padding: 3px var(--spacing-xs);
}
/* Copy history panel full width */
.copy-history-panel {
width: 100%;
right: -100%;
}
.copy-history-panel.active {
right: 0;
}
/* Transform chain items stack vertically */
.transform-chain-item select {
min-width: 100%;
+47 -2
View File
@@ -59,8 +59,20 @@ class MyTool extends Tool {
```html
<div v-if="activeTab === 'mytool'" class="tab-content">
<div class="transform-layout">
<textarea v-model="myInput" @input="processInput"></textarea>
<div v-if="myOutput">{{ myOutput }}</div>
<div class="transform-section">
<div class="section-header">
<h3><i class="fas fa-star"></i> My Tool <small>short subtitle</small></h3>
</div>
<div class="input-section">
<textarea v-model="myInput" @input="processInput"></textarea>
</div>
<div class="tool-toolbar">
<button type="button" class="transform-button tool-primary-btn" @click="processInput">
<i class="fas fa-bolt"></i> Run
</button>
</div>
<div v-if="myOutput" class="output-container">{{ myOutput }}</div>
</div>
</div>
</div>
```
@@ -78,3 +90,36 @@ npm run build:templates # Injects template into index.html
2. **Build**: `inject-tool-templates.js` reads templates and injects into `index.template.html`
3. **Output**: Complete `index.html` with all templates embedded
4. **Browser**: Vue compiles templates at page load (already in DOM)
## Tool UI classes (CSS)
Use the shared vocabulary in [`css/style.css`](../css/style.css) (see the “STANDARD UI COMPONENT TEMPLATES” comment block at the top) so new tools match existing ones.
| Role | Class | Notes |
|------|--------|--------|
| Tool root | `tab-content` | Root `v-if` wrapper for each tool |
| Column layout | `transform-layout` | Flex column with vertical `gap`; `position: relative` for sticky input |
| Card / panel | `transform-section` | Optional tool-specific modifier (e.g. `bijection-section`) for one-off tweaks |
| Page title | `section-header` | Icon + `h3` + optional `<small>` subtitle |
| Intro with long description | `section-header-card` | Use when you need a paragraph under the title (see Gibberish / Splitter) |
| **Action row** | **`tool-toolbar`** | **Primary row for Generate/Copy/Download** — always use this name (not `mutation-actions` / `token-bomb-actions`) |
| Primary CTA | `transform-button tool-primary-btn` | Main “generate” or “run” action inside the toolbar |
| Secondary actions | `action-button copy` / `action-button download` | Toolbar copy/download styling |
| Textarea + floating copy | `textarea-copy-wrap` | Wraps `textarea` + `copy-button` when the copy control is absolutely positioned |
| Small spacing | `u-mb-8`, `u-mb-16`, `u-mt-16` | Prefer these over inline `style` for margins |
Avoid inline `style` on templates; add a small utility or semantic class in `style.css` if you need a new pattern.
### Slide-out sidebars (global shell)
Panels in [`index.template.html`](../index.template.html) (Copy History, Glitch Tokens, End sequences, Advanced Settings) share the same **structural** classes; keep tool-specific names for width/theme hooks only.
| Class | Role |
|--------|------|
| `app-sidebar` | Fixed column from the right: flex column, `transform` slide-in, `active` opens |
| `app-sidebar-header` | Title row + actions (with tool-specific class, e.g. `copy-history-header`) |
| `app-sidebar-body` | Scrollable content (`flex: 1`, `overflow-y: auto`) |
All sidebars share **`--sidebar-width`** (desktop, default `420px`) and **`max-width: min(100%, 90vw)`**. From **768px** breakpoint down they are **full viewport width** (`100%`). Only **stacking** differs: `.unicode-options-panel` uses a higher **`z-index`** (200 desktop, 300 on small screens) so Advanced Settings stays above other panels. Shared shadow: **`--sidebar-edge-shadow`** in `:root`.
**Maintaining `style.css`:** Prefer comma-separated selectors when multiple unrelated classes share the same declarations (e.g. Bijection / PromptCraft / Anti-Classifier card shells). Reuse `:root` tokens such as `--panel-shadow-soft` for repeated shadows instead of copying the same `box-shadow` value.
+13 -13
View File
@@ -114,8 +114,8 @@
</div>
<!-- Copy History Panel -->
<div class="copy-history-panel" :class="{ 'active': showCopyHistory }">
<div class="copy-history-header">
<div class="app-sidebar copy-history-panel" :class="{ 'active': showCopyHistory }">
<div class="app-sidebar-header copy-history-header">
<h3><i class="fas fa-history"></i> Copy History</h3>
<div class="header-actions">
<button
@@ -131,7 +131,7 @@
</button>
</div>
</div>
<div class="copy-history-content">
<div class="app-sidebar-body copy-history-content">
<div v-if="copyHistory.length === 0" class="no-history">
<p>No copy history yet. Use the app features to auto-copy content.</p>
</div>
@@ -158,8 +158,8 @@
</div>
<!-- Glitch Tokens Panel -->
<div class="glitch-token-panel" :class="{ 'active': showGlitchTokenPanel }">
<div class="glitch-token-header">
<div class="app-sidebar glitch-token-panel" :class="{ 'active': showGlitchTokenPanel }">
<div class="app-sidebar-header glitch-token-header">
<h3><i class="fas fa-bug"></i> Glitch Tokens</h3>
<div class="header-actions">
<button class="close-button" @click="toggleGlitchTokenPanel" title="Close glitch tokens">
@@ -167,7 +167,7 @@
</button>
</div>
</div>
<div class="glitch-token-content">
<div class="app-sidebar-body glitch-token-content">
<!-- Filter Section -->
<div class="glitch-token-filters">
<div class="filter-group">
@@ -256,8 +256,8 @@
</div>
<!-- End sequences sidebar (delimiter / stop strings for research) -->
<div class="end-sequence-panel" :class="{ active: showEndSequencePanel }">
<div class="end-sequence-header">
<div class="app-sidebar end-sequence-panel" :class="{ active: showEndSequencePanel }">
<div class="app-sidebar-header end-sequence-header">
<h3><i class="fas fa-stop"></i> End sequences</h3>
<div class="header-actions">
<button class="close-button" type="button" @click="toggleEndSequencePanel" title="Close">
@@ -265,7 +265,7 @@
</button>
</div>
</div>
<div class="end-sequence-content">
<div class="app-sidebar-body end-sequence-content">
<p class="end-sequence-lede">
Strings sometimes used to probe delimiter and termination behavior. Copy into your payloads as needed for authorized testing.
</p>
@@ -296,12 +296,12 @@
</div>
<!-- Advanced Settings Panel (inside app so Vue bindings work) -->
<div id="unicode-options-panel" class="unicode-options-panel">
<div class="unicode-panel-header">
<div id="unicode-options-panel" class="app-sidebar unicode-options-panel">
<div class="app-sidebar-header unicode-panel-header">
<h3><i class="fas fa-sliders-h"></i> Advanced Settings</h3>
<button class="close-button" title="Close Advanced Settings"><i class="fas fa-times"></i></button>
</div>
<div class="unicode-panel-content">
<div class="app-sidebar-body unicode-panel-content">
<!-- OpenRouter API Key -->
<div class="settings-section api-key-section">
<h4><i class="fas fa-key"></i> OpenRouter API Key</h4>
@@ -333,7 +333,7 @@
<!-- Steganography Options -->
<h4><i class="fas fa-user-secret"></i> Steganography Options</h4>
</div>
<div class="unicode-panel-content options-grid steg-adv-panel">
<div class="app-sidebar-body unicode-panel-content options-grid steg-adv-panel">
<label>
Initial Presentation
<select class="steg-initial-presentation">
+1 -1
View File
@@ -35,7 +35,7 @@
</label>
</div>
<div class="token-bomb-actions mutation-actions">
<div class="tool-toolbar">
<button type="button" class="transform-button ac-generate-btn tool-primary-btn" @click="acRun" :disabled="acLoading">
<i :class="acLoading ? 'fas fa-spinner fa-spin' : 'fas fa-bolt'"></i>
{{ acLoading ? 'Generating...' : 'Generate transformations' }}
+1 -1
View File
@@ -45,7 +45,7 @@
<label class="switch neon"><input type="checkbox" v-model="bijectionAutoCopy" /><span>Auto-copy first prompt</span></label>
</div>
<div class="token-bomb-actions mutation-actions">
<div class="tool-toolbar">
<button type="button" class="transform-button bij-generate-btn tool-primary-btn" @click="generateBijectionPrompts">
<i class="fas fa-bolt"></i>
Generate attack prompts
+1 -1
View File
@@ -3,7 +3,7 @@
<div class="input-section">
<div class="input-header">
<label for="decoder-input">Paste any encoded text to decode</label>
<select v-model="selectedDecoder" @change="runUniversalDecode" style="margin-left: 15px; padding: 5px 10px; border-radius: 6px; border: 1px solid var(--input-border); background: var(--button-bg); color: var(--text);">
<select v-model="selectedDecoder" @change="runUniversalDecode" class="decoder-method-select">
<option value="auto">🔍 Auto-detect</option>
<option v-for="transform in getAllTransformsWithReverse()" :key="transform.name" :value="transform.name">
{{ transform.name }}
+5 -5
View File
@@ -29,16 +29,16 @@
<label class="switch neon"><input type="checkbox" v-model="fuzzCasing" /><span>Casing chaos</span></label>
<label class="switch neon"><input type="checkbox" v-model="fuzzEncodeShuffle" /><span>Homoglyph confusables</span></label>
</div>
<div class="token-bomb-actions mutation-actions">
<div class="tool-toolbar">
<button type="button" class="transform-button tool-primary-btn" @click="generateFuzzCases"><i class="fas fa-hammer"></i> Generate Cases</button>
<button class="action-button copy" v-if="fuzzerOutputs.length" @click="copyAllFuzz"><i class="fas fa-copy"></i> Copy All</button>
<button class="action-button download" v-if="fuzzerOutputs.length" @click="downloadFuzz"><i class="fas fa-download"></i> Download</button>
</div>
<div class="output-container" v-if="fuzzerOutputs.length">
<div class="token-tiles fuzzer-list" style="display:flex; flex-direction:column; gap:8px;">
<div v-for="(out, i) in fuzzerOutputs" :key="'fz-'+i" class="token-chip" style="display:flex; align-items:center; gap:8px;">
<span style="opacity:.7; min-width:32px;">#{{ i+1 }}</span>
<textarea :value="out" readonly style="flex:1; min-height:46px;"></textarea>
<div class="token-tiles fuzzer-list">
<div v-for="(out, i) in fuzzerOutputs" :key="'fz-'+i" class="token-chip fuzzer-case-row">
<span class="fuzzer-case-index">#{{ i+1 }}</span>
<textarea class="fuzzer-case-textarea" :value="out" readonly></textarea>
<button class="copy-button" @click="copyToClipboard(out)" title="Copy"><i class="fas fa-copy"></i></button>
</div>
</div>
+9 -9
View File
@@ -10,7 +10,7 @@
</div>
<!-- Mode Selection -->
<div class="button-group" style="margin-bottom: 16px;">
<div class="button-group u-mb-16">
<button
:class="['btn', gibberishMode === 'random' ? 'btn-primary' : 'btn-secondary']"
@click="gibberishMode = 'random'"
@@ -53,7 +53,7 @@
</label>
</div>
<div class="token-bomb-actions mutation-actions">
<div class="tool-toolbar">
<button type="button" class="transform-button tool-primary-btn" @click="sentenceToGibberish"><i class="fas fa-hammer"></i> Generate Gibberish</button>
</div>
@@ -61,15 +61,15 @@
<div class="section-header">
<h4><i class="fas fa-language"></i> Gibberish Output</h4>
</div>
<div style="position: relative;">
<div class="textarea-copy-wrap">
<textarea v-model="gibberishOutput" readonly rows="4"></textarea>
<button class="copy-button" @click="copyToClipboard(gibberishOutput)" title="Copy"><i class="fas fa-copy"></i></button>
</div>
<div class="section-header" style="margin-top: 16px;">
<div class="section-header u-mt-16">
<h4><i class="fas fa-book"></i> Dictionary</h4>
</div>
<div style="position: relative;">
<div class="textarea-copy-wrap">
<textarea v-model="gibberishDictionary" readonly rows="3"></textarea>
<button class="copy-button" @click="copyToClipboard(gibberishDictionary)" title="Copy"><i class="fas fa-copy"></i></button>
</div>
@@ -79,7 +79,7 @@
<!-- Character Removal Mode -->
<div v-if="gibberishMode === 'removal'">
<!-- Sub-mode selection -->
<div class="button-group" style="margin-bottom: 16px;">
<div class="button-group u-mb-16">
<button
:class="['btn', removalSubMode === 'random' ? 'btn-primary' : 'btn-secondary']"
@click="removalSubMode = 'random'"
@@ -128,7 +128,7 @@
</label>
</div>
<div class="token-bomb-actions mutation-actions">
<div class="tool-toolbar">
<button type="button" class="transform-button tool-primary-btn" @click="generateRandomRemovals">
<i class="fas fa-dice"></i> Generate Variations
</button>
@@ -181,7 +181,7 @@
</label>
</div>
<div class="token-bomb-actions mutation-actions">
<div class="tool-toolbar">
<button type="button" class="transform-button tool-primary-btn" @click="generateSpecificRemoval">
<i class="fas fa-eraser"></i> Remove Characters
</button>
@@ -191,7 +191,7 @@
<div class="section-header">
<h4><i class="fas fa-check"></i> Result</h4>
</div>
<div style="position: relative;">
<div class="textarea-copy-wrap">
<textarea v-model="removalSpecificOutput" readonly rows="4"></textarea>
<button class="copy-button" @click="copyToClipboard(removalSpecificOutput)" title="Copy">
<i class="fas fa-copy"></i>
+1 -1
View File
@@ -70,7 +70,7 @@
</div>
</div>
<div class="token-bomb-actions mutation-actions">
<div class="tool-toolbar">
<button type="button" class="transform-button pc-generate-btn tool-primary-btn" @click="pcRunMutation" :disabled="pcLoading">
<i :class="pcLoading ? 'fas fa-spinner fa-spin' : 'fas fa-bolt'"></i>
{{ pcLoading ? 'Generating...' : 'Mutate Prompt' }}
+1 -1
View File
@@ -175,7 +175,7 @@
</div>
<!-- Generate Button -->
<div class="token-bomb-actions splitter-actions">
<div class="tool-toolbar">
<button type="button" class="transform-button tool-primary-btn" @click="generateSplitMessages">
<i class="fas fa-cut"></i> Split Messages
</button>
+4 -4
View File
@@ -97,7 +97,7 @@
<input type="text" v-model="tbCarrierManual" placeholder="e.g., ⚙️ or ::tag::" />
</label>
</div>
<div class="token-bomb-actions mutation-actions">
<div class="tool-toolbar">
<button type="button" class="transform-button tool-primary-btn" @click="generateTokenBomb" title="Build the tokenade with current settings">
<i class="fas fa-hammer"></i> Generate Tokenade
</button>
@@ -118,9 +118,9 @@
<!-- Text Payload Generator (simple) -->
<div class="transform-section">
<h4><i class="fas fa-font"></i> Text Payload Generator</h4>
<label style="display: block; width: 100%; margin-bottom: 15px;">
<label class="tokenade-field-label">
Base text
<input type="text" v-model="tpBase" placeholder="Enter base snippet (e.g., hello)" style="width: 100%;" />
<input type="text" v-model="tpBase" placeholder="Enter base snippet (e.g., hello)" />
</label>
<div class="options-grid">
<label>
@@ -136,7 +136,7 @@
<span>Add zero-width spacing</span>
</label>
</div>
<div class="token-bomb-actions mutation-actions">
<div class="tool-toolbar">
<button type="button" class="transform-button tool-primary-btn" @click="generateTextPayload">
<i class="fas fa-hammer"></i> Generate Text Payload
</button>
+5 -5
View File
@@ -5,7 +5,7 @@
<h3><i class="fas fa-layer-group"></i> Tokenizer Visualization <small>{{ tokenizerEngine }}</small></h3>
<p>Paste text to see how different tokenizers segment it.</p>
</div>
<div class="options-grid" style="margin-bottom:8px;">
<div class="options-grid u-mb-8">
<label>
Engine
<select v-model="tokenizerEngine" @change="runTokenizer">
@@ -33,11 +33,11 @@
<small>{{ tokenizerTokens.length }} total · {{ tokenizerWordCount }} words · {{ tokenizerCharCount }} chars</small>
</h4>
</div>
<div class="token-tiles" v-if="tokenizerTokens.length" style="display:flex; flex-wrap:wrap; gap:6px;">
<div v-for="(tok, i) in tokenizerTokens" :key="i" class="token-chip" style="background:var(--button-bg); border:1px solid var(--input-border); padding:6px 8px; border-radius:6px; font-family:'Fira Code', monospace;">
<span style="opacity:.75; margin-right:6px;">{{ i }}</span>
<div class="token-tiles tokenizer-tiles" v-if="tokenizerTokens.length">
<div v-for="(tok, i) in tokenizerTokens" :key="i" class="token-chip token-chip--tokenizer">
<span class="token-chip__idx">{{ i }}</span>
<span>{{ tok.text }}</span>
<span v-if="tok.id !== undefined" style="opacity:.6; margin-left:6px;">#{{ tok.id }}</span>
<span v-if="tok.id !== undefined" class="token-chip__id">#{{ tok.id }}</span>
</div>
</div>
<div class="output-instructions" v-else>