From: Thomas Walker Lynch Date: Fri, 6 Mar 2026 13:08:19 +0000 (+0000) Subject: nits X-Git-Url: https://git.reasoningtechnology.com/?a=commitdiff_plain;h=7ebd61787845f01532a770caaa0ff4dadc820472;p=RT-style-JS_public nits --- diff --git a/consumer/release/RT/core/body_visibility_hidden.js b/consumer/release/RT/core/body_visibility_hidden.js new file mode 100644 index 0000000..d6a178a --- /dev/null +++ b/consumer/release/RT/core/body_visibility_hidden.js @@ -0,0 +1,12 @@ +/* + Targets the root element to ensure total blackout during load. +*/ +function body_visibility_hidden(){ + const gate = document.createElement('style'); + gate.id = 'rt-visibility-gate'; + gate.textContent = 'html{visibility:hidden !important; background:black !important;}'; + document.head.appendChild(gate); +} + +window.StyleRT = window.StyleRT || {}; +window.StyleRT.body_visibility_hidden = body_visibility_hidden; diff --git a/consumer/release/RT/core/body_visibility_visible.js b/consumer/release/RT/core/body_visibility_visible.js new file mode 100644 index 0000000..ff1c4b6 --- /dev/null +++ b/consumer/release/RT/core/body_visibility_visible.js @@ -0,0 +1,13 @@ +/* + Restores visibility by removing the visibility gate. +*/ +function body_visibility_visible(){ + const gate = document.getElementById('rt-visibility-gate'); + if (gate){ + gate.remove(); + } + document.body.style.visibility = 'visible'; +} + +window.StyleRT = window.StyleRT || {}; +window.StyleRT.body_visibility_visible = body_visibility_visible; diff --git a/consumer/release/RT/core/loader.js b/consumer/release/RT/core/loader.js new file mode 100644 index 0000000..3f4ee68 --- /dev/null +++ b/consumer/release/RT/core/loader.js @@ -0,0 +1,27 @@ +window.StyleRT = window.StyleRT || {}; + +window.StyleRT.include = function(path_identifier){ + let parts_seq = path_identifier.split('/'); + let namespace = parts_seq[0]; + + let module_path = parts_seq.slice(1).join('/'); + + if(module_path === 'theme'){ + let saved_theme = localStorage.getItem('RT_theme_preference'); + if(!saved_theme){ + saved_theme = 'dark_gold'; + localStorage.setItem('RT_theme_preference' ,saved_theme); + } + module_path = 'theme/' + saved_theme; + } + + let base_path = window.StyleRT_namespaces[namespace]; + if(!base_path){ + console.error("Namespace not found: " + namespace); + return; + } + + let full_path = base_path + '/' + module_path + '.js'; + // FIXED: No backslashes in the closing script tag + document.write(''); +}; diff --git a/consumer/release/RT/core/utility.js b/consumer/release/RT/core/utility.js new file mode 100644 index 0000000..258eb28 --- /dev/null +++ b/consumer/release/RT/core/utility.js @@ -0,0 +1,109 @@ +/* + General utilities for the StyleRT library. +*/ + +window.StyleRT = window.StyleRT || {}; + +// --- DEBUG SYSTEM --- +window.StyleRT.debug = { + + // all debug messages enabled +/* + active_tokens: new Set([ + 'style', 'layout', 'pagination' + ,'selector', 'config', 'error' + ,'term' + ]), +*/ + active_tokens: new Set([ + 'term' + ]), + + log: function(token, message) { + if (this.active_tokens.has(token)) { + console.log(`[StyleRT:${token}]`, message); + } + }, + + warn: function(token, message) { + if (this.active_tokens.has(token)) { + console.warn(`[StyleRT:${token}]`, message); + } + }, + + // New: Always log errors regardless of token, but tag them + error: function(token, message) { + console.error(`[StyleRT:${token}] CRITICAL:`, message); + }, + + enable: function(token) { this.active_tokens.add(token); console.log(`Enabled: ${token}`); }, + disable: function(token) { this.active_tokens.delete(token); console.log(`Disabled: ${token}`); } +}; + +// --- UTILITIES --- +window.StyleRT.utility = { + // --- FONT PHYSICS --- + measure_ink_ratio: function(target_font, ref_font = null) { + const debug = window.StyleRT.debug; + debug.log('layout', `Measuring ink ratio for ${target_font}`); + + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + + if (!ref_font) { + const bodyStyle = window.getComputedStyle(document.body); + ref_font = bodyStyle.fontFamily; + } + + const get_metrics = (font) => { + ctx.font = '100px ' + font; + const metrics = ctx.measureText('M'); + return { + ascent: metrics.actualBoundingBoxAscent, + descent: metrics.actualBoundingBoxDescent + }; + }; + + const ref_m = get_metrics(ref_font); + const target_m = get_metrics(target_font); + + const ratio = ref_m.ascent / target_m.ascent; + // debug.log('layout', `Ink Ratio calculated: ${ratio.toFixed(3)}`); + + return { + ratio: ratio, + baseline_diff: ref_m.descent - target_m.descent + }; + }, + + // --- COLOR PHYSICS --- + is_color_light: function(color_string) { + const debug = window.StyleRT.debug; + + // 1. HSL Check + if (color_string.startsWith('hsl')) { + const numbers = color_string.match(/\d+/g); + if (numbers && numbers.length >= 3) { + const lightness = parseInt(numbers[2]); + return lightness > 50; + } + } + + // 2. RGB Check + const rgb = color_string.match(/\d+/g); + if (!rgb) { + // debug.warn('color_layout', `Failed to parse color: "${color_string}". Defaulting to Light.`); + return true; + } + + const r = parseInt(rgb[0]); + const g = parseInt(rgb[1]); + const b = parseInt(rgb[2]); + const luma = (r * 299 + g * 587 + b * 114) / 1000; + return luma > 128; + }, + + is_block_content: function(element) { + return element.textContent.trim().includes('\n'); + } +}; diff --git a/consumer/release/RT/element/TOC.js b/consumer/release/RT/element/TOC.js new file mode 100644 index 0000000..eb246f5 --- /dev/null +++ b/consumer/release/RT/element/TOC.js @@ -0,0 +1,102 @@ +/* + Processes tags. + Populates each with headings found below it. + + Attributes: + level="N" : Explicitly sets the target heading level (1-6). + e.g., level="1" collects H1s. level="2" collects H2s. + Stops collecting if it hits a heading of (level - 1) or higher. + + Default (No attribute): + Context Aware. Looks backwards for the nearest heading H(N). + Targets H(N+1). Stops at the next H(N). +*/ +window.StyleRT = window.StyleRT || {}; + +window.StyleRT.TOC = function(){ + const debug = window.StyleRT.debug || { log: function(){} }; + const TOC_seq = document.querySelectorAll('rt-toc'); + + TOC_seq.forEach( (container ,TOC_index) => { + container.style.display = 'block'; + + // 1. Determine Target Level + const attr_level = parseInt( container.getAttribute('level') ); + let target_level; + + if( !isNaN(attr_level) ){ + // EXPLICIT MODE + target_level = attr_level; + if(debug.log) debug.log('TOC' ,`TOC #${TOC_index} explicit target: H${target_level}`); + } else { + // IMPLICIT / CONTEXT MODE + let context_level = 0; // Default 0 (Root) + let prev = container.previousElementSibling; + while(prev){ + const match = prev.tagName.match(/^H([1-6])$/); + if(match){ + context_level = parseInt( match[1] ); + break; + } + prev = prev.previousElementSibling; + } + target_level = context_level + 1; + if(debug.log) debug.log('TOC' ,`TOC #${TOC_index} context implied target: H${target_level}`); + } + + // Stop condition: Stop if we hit a heading that is a "parent" or "sibling" of the context. + // Mathematically: Stop if found_level < target_level. + const stop_threshold = target_level; + + // 2. Setup Container + container.innerHTML = ''; + const title = document.createElement('h1'); + // Title logic: If targeting H1, the element serves as a Main TOC. Otherwise the element serves as a Section TOC. + title.textContent = target_level === 1 ? 'Table of Contents' : 'Section Contents'; + title.style.textAlign = 'center'; + container.appendChild(title); + + const list = document.createElement('ul'); + list.style.listStyle = 'none'; + list.style.paddingLeft = '0'; + container.appendChild(list); + + // 3. Scan Forward + let next_el = container.nextElementSibling; + while(next_el){ + const match = next_el.tagName.match(/^H([1-6])$/); + if(match){ + const found_level = parseInt( match[1] ); + + // STOP Logic: + // If we are looking for H2s, we stop if we hit an H1 (level 1). + // If we are looking for H1s, we stop if we hit nothing (level 0). + if(found_level < target_level){ + break; + } + + // COLLECT Logic: + if(found_level === target_level){ + if(!next_el.id) next_el.id = `TOC-ref-${TOC_index}-${found_level}-${list.children.length}`; + + const li = document.createElement('li'); + li.style.marginBottom = '0.5rem'; + + const a = document.createElement('a'); + a.href = `#${next_el.id}`; + a.textContent = next_el.textContent; + a.style.textDecoration = 'none'; + a.style.color = 'inherit'; + a.style.display = 'block'; + + a.onmouseover = () => a.style.color = 'var(--rt-brand-primary)'; + a.onmouseout = () => a.style.color = 'inherit'; + + li.appendChild(a); + list.appendChild(li); + } + } + next_el = next_el.nextElementSibling; + } + }); +}; diff --git a/consumer/release/RT/element/code.js b/consumer/release/RT/element/code.js new file mode 100644 index 0000000..7e6dca8 --- /dev/null +++ b/consumer/release/RT/element/code.js @@ -0,0 +1,129 @@ +/* + Processes tags. + Uses the central config or CSS variables from the theme. + + Removes common indent from lines of code. +*/ +function code() { + const RT = window.StyleRT; + const U = RT.utility; + const debug = RT.debug; + + debug.log('code', 'Starting render cycle.'); + + const metrics = U.measure_ink_ratio('monospace'); + + document.querySelectorAll('rt-code').forEach((el) => { + el.style.fontFamily = 'monospace'; + + const computed = window.getComputedStyle(el); + const accent = computed.getPropertyValue('--rt-accent').trim() || 'gold'; + + const is_block = U.is_block_content(el); + const parentColor = computed.color; + const is_text_light = U.is_color_light(parentColor); + + const alpha = is_block ? 0.08 : 0.15; + const overlay = is_text_light ? `rgba(255,255,255,${alpha})` : `rgba(0,0,0,${alpha})`; + const text_color = is_text_light ? '#ffffff' : '#000000'; + + el.style.backgroundColor = overlay; + + if (is_block) { + el.style.display = 'block'; + + // --- Tag-Relative Auto-Dedent Logic --- + + // 1. Get Tag Indentation (The Anchor) + let tagIndent = ''; + const prevNode = el.previousSibling; + if (prevNode && prevNode.nodeType === 3) { + const prevText = prevNode.nodeValue; + const lastNewLineIndex = prevText.lastIndexOf('\n'); + if (lastNewLineIndex !== -1) { + tagIndent = prevText.substring(lastNewLineIndex + 1); + } else if (/^\s*$/.test(prevText)) { + tagIndent = prevText; + } + } + + // 2. Calculate Common Leading Whitespace from Content + const rawLines = el.textContent.split('\n'); + + // Filter out empty lines for calculation purposes so they don't break the logic + const contentLines = rawLines.filter(line => line.trim().length > 0); + + let commonIndent = null; + + if (contentLines.length > 0) { + // Assume the first line sets the standard + const firstMatch = contentLines[0].match(/^\s*/); + commonIndent = firstMatch ? firstMatch[0] : ''; + + // Reduce the commonIndent if subsequent lines have LESS indentation + for (let i = 1; i < contentLines.length; i++) { + const line = contentLines[i]; + // Determine how much of commonIndent this line shares + let j = 0; + while (j < commonIndent.length && j < line.length && commonIndent[j] === line[j]) { + j++; + } + commonIndent = commonIndent.substring(0, j); + if (commonIndent.length === 0) break; // Optimization + } + } else { + commonIndent = ''; + } + + // 3. Process Content + // Rule: Only strip if the Common Indent contains the Tag Indent (Safety Check) + // This handles the Emacs case: Tag is " ", Common is " ". " " starts with " ". + // We strip " ", leaving the code flush left. + let finalString = ''; + + if (commonIndent.length > 0 && commonIndent.startsWith(tagIndent)) { + const cleanedLines = rawLines.map(line => { + // Strip the common indent from valid lines + return line.startsWith(commonIndent) ? line.replace(commonIndent, '') : line; + }); + + // Remove artifact lines (first/last empty lines) + if (cleanedLines.length > 0 && cleanedLines[0].length === 0) { + cleanedLines.shift(); + } + if (cleanedLines.length > 0 && cleanedLines[cleanedLines.length - 1].trim().length === 0) { + cleanedLines.pop(); + } + finalString = cleanedLines.join('\n'); + } else { + // Fallback: Code is to the left of the tag or weirdly formatted. + // Just trim the wrapper newlines. + finalString = el.textContent.trim(); + } + + el.textContent = finalString; + // --- End Indentation Logic --- + + el.style.whiteSpace = 'pre'; + el.style.fontSize = (parseFloat(computed.fontSize) * metrics.ratio * 0.95) + 'px'; + el.style.padding = '1.2rem'; + el.style.margin = '1.5rem 0'; + el.style.borderLeft = `4px solid ${accent}`; + el.style.color = 'inherit'; + } else { + el.style.display = 'inline'; + const exactPx = parseFloat(computed.fontSize) * metrics.ratio * 1.0; + el.style.fontSize = exactPx + 'px'; + el.style.padding = '0.1rem 0.35rem'; + el.style.borderRadius = '3px'; + const offsetPx = metrics.baseline_diff * (exactPx / 100); + el.style.verticalAlign = offsetPx + 'px'; + el.style.color = text_color; + } + }); + + debug.log('code', 'Render cycle complete.'); +} + +window.StyleRT = window.StyleRT || {}; +window.StyleRT.code = code; diff --git a/consumer/release/RT/element/math.js b/consumer/release/RT/element/math.js new file mode 100644 index 0000000..1e2ae8f --- /dev/null +++ b/consumer/release/RT/element/math.js @@ -0,0 +1,35 @@ +/* + Processes tags. + JavaScript: math() + HTML Tag: (parsed as rt-math) +*/ +function math(){ + // querySelector treats 'rt-math' as case-insensitive for the tag + document.querySelectorAll('rt-math').forEach(el => { + if (el.textContent.startsWith('$')) return; + + const is_block = el.parentElement.tagName === 'DIV' || + el.textContent.includes('\n') || + el.parentElement.childNodes.length === 1; + + const delimiter = is_block ? '$$' : '$'; + el.style.display = is_block ? 'block' : 'inline'; + el.textContent = `${delimiter}${el.textContent.trim()}${delimiter}`; + }); + + // MathJax must find its config at window.MathJax + window.MathJax = { + tex: { + inlineMath: [['$', '$']], + displayMath: [['$$', '$$']] + } + }; + + const script = document.createElement('script'); + script.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js'; + script.async = true; + document.head.appendChild(script); +} + +window.StyleRT = window.StyleRT || {}; +window.StyleRT.math = math; diff --git a/consumer/release/RT/element/term.js b/consumer/release/RT/element/term.js new file mode 100644 index 0000000..4ebc634 --- /dev/null +++ b/consumer/release/RT/element/term.js @@ -0,0 +1,107 @@ +/* + Processes and tags. + - Styles only the first occurrence of a unique term/neologism. + - The "-em" variants (e.g., ) are always styled. + - Automatically generates IDs for first occurrences for future indexing. +*/ + +window.StyleRT = window.StyleRT || {}; + +window.StyleRT.term = function() { + const RT = window.StyleRT; + + const debug = RT.debug || { + log: function() {} + ,warn: function() {} + ,error: function() {} + }; + + const DEBUG_TOKEN_S = 'term'; + + try { + // Track seen terms so only the first occurrence is decorated + const seen_terms_dpa = new Set(); + + const apply_style = (el, is_neologism_b) => { + el.style.fontStyle = 'italic'; + el.style.fontWeight = is_neologism_b ? '600' : '500'; + el.style.color = is_neologism_b + ? 'var(--rt-brand-secondary)' + : 'var(--rt-brand-primary)'; + el.style.paddingRight = '0.1em'; // Compensation for italic slant + el.style.display = 'inline'; + }; + + const clear_style = (el) => { + el.style.fontStyle = 'normal'; + el.style.color = 'inherit'; + el.style.fontWeight = 'inherit'; + el.style.paddingRight = ''; + el.style.display = ''; + }; + + const selector_s = [ + 'rt-term' + ,'rt-term-em' + ,'rt-neologism' + ,'rt-neologism-em' + ].join(','); + + const tags_dpa = document.querySelectorAll(selector_s); + + debug.log(DEBUG_TOKEN_S, `Scanning ${tags_dpa.length} term tags`); + + tags_dpa.forEach(el => { + const tag_name_s = el.tagName.toLowerCase(); + const is_neologism_b = tag_name_s.includes('neologism'); + const is_explicit_em_b = tag_name_s.endsWith('-em'); + + const term_text_raw_s = (el.textContent || '').trim(); + if (!term_text_raw_s.length) { + debug.warn(DEBUG_TOKEN_S, `Empty term tag encountered: <${tag_name_s}>`); + return; + } + + // Normalize text for uniqueness tracking + const term_norm_s = term_text_raw_s.toLowerCase(); + + // Slug for ID generation (simple + stable) + const slug_s = term_norm_s.replace(/\s+/g, '-'); + + const is_first_occurrence_b = !seen_terms_dpa.has(term_norm_s); + + if (is_explicit_em_b || is_first_occurrence_b) { + apply_style(el, is_neologism_b); + + if (!is_explicit_em_b && is_first_occurrence_b) { + seen_terms_dpa.add(term_norm_s); + + if (!el.id) { + el.id = `def-${is_neologism_b ? 'neo-' : ''}${slug_s}`; + debug.log( + DEBUG_TOKEN_S + ,`First occurrence: "${term_norm_s}" -> id="${el.id}"` + ); + } else { + debug.log( + DEBUG_TOKEN_S + ,`First occurrence: "${term_norm_s}" (existing id="${el.id}")` + ); + } + } else if (is_explicit_em_b) { + debug.log( + DEBUG_TOKEN_S + ,`Emphasized occurrence: "${term_norm_s}" (<${tag_name_s}>)` + ); + } + } else { + // Subsequent mentions render as normal prose + clear_style(el); + } + }); + + debug.log(DEBUG_TOKEN_S, `Unique terms defined: ${seen_terms_dpa.size}`); + } catch (e) { + debug.error('error', `term failed: ${e && e.message ? e.message : String(e)}`); + } +}; diff --git a/consumer/release/RT/element/theme_selector.js b/consumer/release/RT/element/theme_selector.js new file mode 100644 index 0000000..05759d2 --- /dev/null +++ b/consumer/release/RT/element/theme_selector.js @@ -0,0 +1,27 @@ +class ThemeSelector extends HTMLElement{ + connectedCallback(){ + let current_theme = localStorage.getItem('RT_theme_preference'); + if(!current_theme){ + current_theme = 'dark_gold'; + } + + this.innerHTML = ` +
+ Theme Selection
+
+ +
+ `; + + this.addEventListener( 'change' ,(e) => { + localStorage.setItem('RT_theme_preference' ,e.target.value); + location.reload(); + } ); + } +} + +customElements.define('rt-theme-selector' ,ThemeSelector); diff --git a/consumer/release/RT/element/title.js b/consumer/release/RT/element/title.js new file mode 100644 index 0000000..fefd991 --- /dev/null +++ b/consumer/release/RT/element/title.js @@ -0,0 +1,60 @@ +/* + Processes tags. + Generates a standard document header block. + + Usage: + +*/ +window.StyleRT = window.StyleRT || {}; + +window.StyleRT.title = function() { + const debug = window.StyleRT.debug || { log: function(){} }; + + document.querySelectorAll('rt-title').forEach(el => { + const title = el.getAttribute('title') || 'Untitled Document'; + const author = el.getAttribute('author'); + const date = el.getAttribute('date'); + + if (debug.log) debug.log('title', `Generating title block: ${title}`); + + // Container + const container = document.createElement('div'); + container.style.textAlign = 'center'; + container.style.marginBottom = '3rem'; + container.style.marginTop = '2rem'; + container.style.borderBottom = '1px solid var(--rt-border-default)'; + container.style.paddingBottom = '1.5rem'; + + // Main Title (H1) + const h1 = document.createElement('h1'); + h1.textContent = title; + h1.style.margin = '0 0 0.8rem 0'; + h1.style.border = 'none'; // Override standard H1 border + h1.style.padding = '0'; + h1.style.color = 'var(--rt-brand-primary)'; + h1.style.fontSize = '2.5em'; + h1.style.lineHeight = '1.1'; + h1.style.letterSpacing = '-0.03em'; + + container.appendChild(h1); + + // Metadata Row (Author | Date) + if (author || date) { + const meta = document.createElement('div'); + meta.style.color = 'var(--rt-content-muted)'; + meta.style.fontStyle = 'italic'; + meta.style.fontSize = '1.1em'; + meta.style.fontFamily = '"Georgia", "Times New Roman", serif'; // Classy serif + + const parts = []; + if (author) parts.push(`${author}`); + if (date) parts.push(date); + + meta.innerHTML = parts.join('  —  '); + container.appendChild(meta); + } + + // Replace the raw tag with the generated block + el.replaceWith(container); + }); +}; diff --git a/consumer/release/RT/layout/article_tech_ref.js b/consumer/release/RT/layout/article_tech_ref.js new file mode 100644 index 0000000..795e2e1 --- /dev/null +++ b/consumer/release/RT/layout/article_tech_ref.js @@ -0,0 +1,84 @@ +(function(){ + const RT = window.StyleRT = window.StyleRT || {}; + + // 1. Declare Dependencies with updated core/ and layout/ paths + RT.include('RT/core/utility'); + RT.include('RT/element/math'); + RT.include('RT/element/code'); + RT.include('RT/element/term'); + RT.include('RT/element/TOC'); + RT.include('RT/element/title'); + RT.include('RT/element/theme_selector'); + RT.include('RT/layout/paginate_by_element'); + RT.include('RT/layout/page_fixed_glow'); + RT.include('RT/core/body_visibility_visible'); + + // 2. The Typography Layout (Adapted from the original) + RT.article = function(){ + RT.config = RT.config || {}; + RT.config.article = { + font_family: '"Noto Sans", "Segoe UI", "Helvetica Neue", sans-serif' + ,line_height: "1.8" + ,font_size: "16px" + ,font_weight: "400" + ,max_width: "820px" + ,margin: "0 auto" + }; + + if( RT.config.theme && RT.config.theme.meta_is_dark === false ){ + RT.config.article.font_weight = "600"; + } + + const conf = RT.config.article; + const article_seq = document.querySelectorAll("RT-article"); + + if(article_seq.length === 0) return; + + for(let i = 0; i < article_seq.length; i++){ + let style = article_seq[i].style; + style.display = "block"; + style.fontFamily = conf.font_family; + style.fontSize = conf.font_size; + style.lineHeight = conf.line_height; + style.fontWeight = conf.font_weight; + style.maxWidth = conf.max_width; + style.margin = conf.margin; + style.padding = "0 20px"; + style.color = "var(--rt-content-main)"; + } + + // CSS injection for headers, lists, etc., goes here + // (Omitted for brevity, but carried over from the original file) + }; + + // 3. The Execution Sequence + const run_semantics = function(){ + if(RT.theme) RT.theme(); + RT.article(); + + // Call the newly renamed element functions + if(RT.title) RT.title(); + if(RT.term) RT.term(); + if(RT.math) RT.math(); + if(RT.code) RT.code(); + + // Check for MathJax typesetting + if(window.MathJax && MathJax.Hub && MathJax.Hub.Queue){ + MathJax.Hub.Queue( ["Typeset" ,MathJax.Hub] ,run_layout ); + }else{ + run_layout(); + } + }; + + const run_layout = function(){ + if(RT.TOC) RT.TOC(); + if(RT.paginate_by_element) RT.paginate_by_element(); + if(RT.page) RT.page(); + if(RT.body_visibility_visible) RT.body_visibility_visible(); + }; + + // 4. Bind to DOM Ready + // This replaces the need to put a script at the bottom of the HTML body + document.addEventListener( 'DOMContentLoaded' ,run_semantics ); + +})(); diff --git a/consumer/release/RT/layout/memo_State.js b/consumer/release/RT/layout/memo_State.js new file mode 100644 index 0000000..a2eb279 --- /dev/null +++ b/consumer/release/RT/layout/memo_State.js @@ -0,0 +1,70 @@ +/* + + + + + + + +*/ + +(function(){ + const RT = window.StyleRT = window.StyleRT || {}; + + // 1. Declare Dependencies + RT.include('RT/core/utility'); + RT.include('RT/element/title'); + RT.include('RT/element/term'); + RT.include('RT/element/TOC'); + RT.include('RT/core/body_visibility_visible'); + + // 2. The Typography Layout + RT.memo_state_dept = function(){ + const body = document.body; + const html = document.documentElement; + + // Force strict print colors regardless of user system settings + html.style.backgroundColor = "white"; + body.style.backgroundColor = "white"; + body.style.color = "black"; + + // Target the new semantic tag + const memo_seq = document.querySelectorAll("RT-memo"); + if(memo_seq.length === 0) return; + + for(let i = 0; i < memo_seq.length; i++){ + let style = memo_seq[i].style; + style.display = "block"; + style.fontFamily = '"Times New Roman", Times, serif'; + style.fontSize = "12pt"; + style.lineHeight = "1.15"; + // 8.5 inch standard width minus 1-inch margins on each side + style.maxWidth = "6.5in"; + style.margin = "1in auto"; + style.padding = "0"; + style.textAlign = "left"; + style.color = "black"; + } + }; + + // 3. The Execution Sequence + const run_semantics = function(){ + RT.memo_state_dept(); + + if(RT.title) RT.title(); + if(RT.term) RT.term(); + if(RT.TOC) RT.TOC(); + + run_layout(); + }; + + const run_layout = function(){ + if(RT.body_visibility_visible) RT.body_visibility_visible(); + }; + + // 4. Bind to DOM Ready + document.addEventListener('DOMContentLoaded' ,run_semantics); + +})(); diff --git a/consumer/release/RT/layout/page_fixed_glow.js b/consumer/release/RT/layout/page_fixed_glow.js new file mode 100644 index 0000000..91bd648 --- /dev/null +++ b/consumer/release/RT/layout/page_fixed_glow.js @@ -0,0 +1,88 @@ +/* + Page Layout: Fixed Glow + Standard: Theme 1.0 + Description: A fixed-height container with a glowing border effect that matches the active theme. +*/ +(function(){ + const RT = window.StyleRT = window.StyleRT || {}; + + // Function name stays generic so the orchestrator can call RT.page() regardless of file choice + RT.page = function() { + RT.config = RT.config || {}; + + // Default Configuration + // We use CSS Variables here so the Theme controls the actual colors. + const defaults = { + width: "100%" + ,height: "1056px" + ,padding: "3rem" + ,margin: "4rem auto" + + // Dynamic Theme Bindings + ,bg_color: "var(--rt-surface-0)" // Black (Dark) or Cream (Light) + ,border_color: "var(--rt-brand-primary)" // The Main Accent Color + ,text_color: "var(--rt-brand-primary)" // Page Number Color + + // The Glow: Uses the primary brand color for the shadow + ,shadow: "drop-shadow(0px 0px 15px var(--rt-brand-primary))" + }; + + // Merge defaults + RT.config.page = Object.assign({}, defaults, RT.config.page || {}); + + const conf = RT.config.page; + const style_id = 'rt-page-fixed-glow'; + + if (!document.getElementById(style_id)) { + const style_el = document.createElement('style'); + style_el.id = style_id; + + style_el.textContent = ` + /* Reset page counter on the article container */ + rt-article { + counter-reset: rt-page-counter; + } + + rt-page { + display: block; + position: relative; + box-sizing: border-box; + overflow: hidden; + + /* Dimensions */ + width: ${conf.width}; + height: ${conf.height}; + margin: ${conf.margin}; + padding: ${conf.padding}; + + /* Theming */ + background-color: ${conf.bg_color}; + border: 1px solid ${conf.border_color}; + + /* The "Glow" Effect */ + filter: ${conf.shadow}; + + /* Counter Increment */ + counter-increment: rt-page-counter; + } + + /* Page Numbering */ + rt-page::after { + content: "Page " counter(rt-page-counter); + position: absolute; + bottom: 1.5rem; + right: 3rem; + + font-family: "Noto Sans", sans-serif; + font-size: 0.9rem; + font-weight: bold; + + color: ${conf.text_color}; + opacity: 0.8; + pointer-events: none; /* Prevent interference with clicks */ + } + `; + document.head.appendChild(style_el); + } + }; +})(); diff --git a/consumer/release/RT/layout/paginate_by_element.js b/consumer/release/RT/layout/paginate_by_element.js new file mode 100644 index 0000000..e085148 --- /dev/null +++ b/consumer/release/RT/layout/paginate_by_element.js @@ -0,0 +1,72 @@ +/* + Layout Paginator: paginate_by_element +*/ +window.StyleRT = window.StyleRT || {}; + +window.StyleRT.paginate_by_element = function() { + const RT = window.StyleRT; + + // Fix: Read safely without overwriting the config namespace + const page_conf = (RT.config && RT.config.page) ? RT.config.page : {}; + const page_height_limit = page_conf.height_limit || 1000; + + const article_seq = document.querySelectorAll("RT-article"); + + // HURDLE: Error if no articles found to paginate + if(article_seq.length === 0) { + RT.debug.error('pagination', 'No elements found. Pagination aborted.'); + return; + } + + article_seq.forEach( (article) => { + const raw_elements = Array.from(article.children).filter(el => + !['SCRIPT', 'STYLE', 'RT-PAGE'].includes(el.tagName) + ); + + if(raw_elements.length === 0) return; + + const pages = []; + let current_batch = []; + let current_h = 0; + + const get_el_height = (el) => { + const rect = el.getBoundingClientRect(); + const style = window.getComputedStyle(el); + const margin = parseFloat(style.marginTop) + parseFloat(style.marginBottom); + return (rect.height || 0) + (margin || 0); + }; + + for (let i = 0; i < raw_elements.length; i++) { + const el = raw_elements[i]; + const h = get_el_height(el); + const is_heading = /^H[1-6]/.test(el.tagName); + + let total_required_h = h; + if (is_heading && i + 1 < raw_elements.length) { + total_required_h += get_el_height(raw_elements[i + 1]); + } + + if (current_h + total_required_h > page_height_limit && current_batch.length > 0) { + pages.push(current_batch); + current_batch = []; + current_h = 0; + } + + current_batch.push(el); + current_h += h; + } + + if (current_batch.length > 0) pages.push(current_batch); + + article.innerHTML = ''; + + pages.forEach( (list, index) => { + const page_el = document.createElement('rt-page'); + page_el.id = `page-${index+1}`; + list.forEach(item => page_el.appendChild(item)); + article.appendChild(page_el); + }); + + if (RT.debug) RT.debug.log('pagination', `Article paginated into ${pages.length} pages.`); + }); +}; diff --git a/consumer/release/RT/theme/dark_gold.js b/consumer/release/RT/theme/dark_gold.js new file mode 100644 index 0000000..f576147 --- /dev/null +++ b/consumer/release/RT/theme/dark_gold.js @@ -0,0 +1,94 @@ +/* + Theme: Inverse Wheat (Dark) + Standard: Theme 1.0 + Description: High contrast Amber on Deep Charcoal. +*/ +( function(){ + const RT = window.StyleRT = window.StyleRT || {}; + + RT.theme = function(){ + RT.config = RT.config || {}; + + // THEME 1.0 DATA CONTRACT + RT.config.theme = { + meta_is_dark: true + ,meta_name: "Inverse Wheat" + + // --- SURFACES (Depth & Container Hierarchy) --- + ,surface_0: "hsl(0, 0%, 5%)" // App Background (Deepest) + ,surface_1: "hsl(0, 0%, 10%)" // Sidebar / Nav / Panels + ,surface_2: "hsl(0, 0%, 14%)" // Cards / Floating Elements + ,surface_3: "hsl(0, 0%, 18%)" // Modals / Dropdowns / Popovers + ,surface_input: "hsl(0, 0%, 12%)" // Form Inputs + ,surface_code: "hsl(0, 0%, 11%)" // Code Block Background + ,surface_select: "hsl(45, 100%, 15%)" // Text Selection Highlight + + // --- CONTENT (Text & Icons) --- + ,content_main: "hsl(50, 60%, 85%)" // Primary Reading Text + ,content_muted: "hsl(36, 15%, 60%)" // Metadata, subtitles + ,content_subtle: "hsl(36, 10%, 40%)" // Placeholders, disabled states + ,content_inverse: "hsl(0, 0%, 5%)" // Text on high-contrast buttons + + // --- BRAND & ACTION (The "Wheat" Identity) --- + ,brand_primary: "hsl(45, 100%, 50%)" // Main Action / H1 / Focus Ring + ,brand_secondary: "hsl(38, 90%, 65%)" // Secondary Buttons / H2 + ,brand_tertiary: "hsl(30, 60%, 70%)" // Accents / H3 + ,brand_link: "hsl(48, 100%, 50%)" // Hyperlinks (High Visibility) + + // --- BORDERS & DIVIDERS --- + ,border_faint: "hsl(36, 20%, 15%)" // Subtle separation + ,border_default: "hsl(36, 20%, 25%)" // Standard Card Borders + ,border_strong: "hsl(36, 20%, 40%)" // Active states / Inputs + + // --- STATE & FEEDBACK (Earth Tones) --- + ,state_success: "hsl(100, 50%, 45%)" // Olive Green + ,state_warning: "hsl(35, 90%, 55%)" // Burnt Orange + ,state_error: "hsl(0, 60%, 55%)" // Brick Red + ,state_info: "hsl(200, 40%, 55%)" // Slate Blue + + // --- SYNTAX HIGHLIGHTING (For Code) --- + ,syntax_keyword: "hsl(35, 100%, 65%)" // Orange + ,syntax_string: "hsl(75, 50%, 60%)" // Sage Green + ,syntax_func: "hsl(45, 90%, 70%)" // Light Gold + ,syntax_comment: "hsl(36, 15%, 45%)" // Brown/Gray + }; + + // --- APPLY THEME --- + const palette = RT.config.theme; + const body = document.body; + const html = document.documentElement; + + // 1. Paint Base + html.style.backgroundColor = palette.surface_0; + body.style.backgroundColor = palette.surface_0; + body.style.color = palette.content_main; + + // 2. Export Variables (Standardization) + const s = body.style; + for (const [key, value] of Object.entries(palette)) { + s.setProperty(`--rt-${key.replace(/_/g, '-')}`, value); + } + + // 3. Global Overrides + const style_id = 'rt-global-overrides'; + if (!document.getElementById(style_id)) { + const style = document.createElement('style'); + style.id = style_id; + style.textContent = ` + ::selection { background: var(--rt-surface-select); color: var(--rt-brand-primary); } + ::-moz-selection { background: var(--rt-surface-select); color: var(--rt-brand-primary); } + + ::-webkit-scrollbar { width: 12px; } + ::-webkit-scrollbar-track { background: var(--rt-surface-0); } + ::-webkit-scrollbar-thumb { + background: var(--rt-border-default); + border: 2px solid var(--rt-surface-0); + border-radius: 8px; + } + ::-webkit-scrollbar-thumb:hover { background: var(--rt-brand-secondary); } + `; + document.head.appendChild(style); + } + }; + +} )(); diff --git a/consumer/release/RT/theme/light.js b/consumer/release/RT/theme/light.js new file mode 100644 index 0000000..4b5eea4 --- /dev/null +++ b/consumer/release/RT/theme/light.js @@ -0,0 +1,70 @@ +/* + Theme: Classic Wheat (Light) + Standard: Theme 1.0 + Description: Warm paper tones with Burnt Orange accents. +*/ +( function(){ + const RT = window.StyleRT = window.StyleRT || {}; + + RT.theme_light = function(){ + RT.config = RT.config || {}; + + // THEME 1.0 DATA CONTRACT + RT.config.theme = { + meta_is_dark: false + ,meta_name: "Classic Wheat" + + // --- SURFACES --- + ,surface_0: "hsl(40, 30%, 94%)" // App Background (Cream/Linen) + ,surface_1: "hsl(40, 25%, 90%)" // Sidebar (Slightly darker beige) + ,surface_2: "hsl(40, 20%, 98%)" // Cards (Lighter, almost white) + ,surface_3: "hsl(0, 0%, 100%)" // Modals (Pure White) + ,surface_input: "hsl(40, 20%, 98%)" // Form Inputs + ,surface_code: "hsl(40, 15%, 90%)" // Code Block Background + ,surface_select: "hsl(45, 100%, 85%)" // Text Selection Highlight + + // --- CONTENT --- + ,content_main: "hsl(30, 20%, 20%)" // Deep Umber (Not Black) + ,content_muted: "hsl(30, 15%, 45%)" // Medium Brown + ,content_subtle: "hsl(30, 10%, 65%)" // Light Brown/Gray + ,content_inverse: "hsl(40, 30%, 94%)" // Text on dark buttons + + // --- BRAND & ACTION --- + ,brand_primary: "hsl(30, 90%, 35%)" // Burnt Orange (Action) + ,brand_secondary: "hsl(35, 70%, 45%)" // Rust / Gold + ,brand_tertiary: "hsl(25, 60%, 55%)" // Copper + ,brand_link: "hsl(30, 100%, 35%)" // Link Color + + // --- BORDERS --- + ,border_faint: "hsl(35, 20%, 85%)" + ,border_default: "hsl(35, 20%, 75%)" + ,border_strong: "hsl(35, 20%, 55%)" + + // --- STATE & FEEDBACK --- + ,state_success: "hsl(100, 40%, 40%)" // Forest Green + ,state_warning: "hsl(30, 90%, 50%)" // Persimmon + ,state_error: "hsl(0, 60%, 45%)" // Crimson + ,state_info: "hsl(200, 50%, 45%)" // Navy Blue + + // --- SYNTAX --- + ,syntax_keyword: "hsl(20, 90%, 45%)" // Rust + ,syntax_string: "hsl(100, 35%, 35%)" // Ivy Green + ,syntax_func: "hsl(300, 30%, 40%)" // Muted Purple + ,syntax_comment: "hsl(35, 10%, 60%)" // Light Brown + }; + + // --- APPLY THEME --- + const palette = RT.config.theme; + const body = document.body; + const html = document.documentElement; + + html.style.backgroundColor = palette.surface_0; + body.style.backgroundColor = palette.surface_0; + body.style.color = palette.content_main; + + const s = body.style; + for (const [key, value] of Object.entries(palette)) { + s.setProperty(`--rt-${key.replace(/_/g, '-')}`, value); + } + }; +} )(); diff --git a/consumer/release/RT/theme/light_gold.js b/consumer/release/RT/theme/light_gold.js new file mode 100644 index 0000000..206f3da --- /dev/null +++ b/consumer/release/RT/theme/light_gold.js @@ -0,0 +1,103 @@ +/* + Theme: Golden Wheat (Light) - "Spanish Gold Edition" + File: style/theme-light-gold.js + Standard: Theme 1.0 + Description: Light Parchment background with Oxblood Red ink. +*/ +( function(){ + const RT = window.StyleRT = window.StyleRT || {}; + + RT.theme = function(){ + RT.config = RT.config || {}; + + RT.config.theme = { + meta_is_dark: false + ,meta_name: "Golden Wheat (Yellow)" + + // --- SURFACES (Light Parchment) --- + // Shifted lightness up to 94% for a "whiter" feel that still holds the yellow tint. + ,surface_0: "hsl(48, 50%, 94%)" // Main Page: Fine Parchment + ,surface_1: "hsl(48, 40%, 90%)" // Panels: Slightly darker + ,surface_2: "hsl(48, 30%, 97%)" // Cards: Very light + ,surface_3: "hsl(0, 0%, 100%)" // Popups + ,surface_input: "hsl(48, 20%, 96%)" + ,surface_code: "hsl(48, 25%, 88%)" // Distinct Code BG + ,surface_select: "hsl(10, 70%, 85%)" // Red Highlight + + // --- CONTENT (Deep Ink) --- + ,content_main: "hsl(10, 25%, 7%)" // Deep Warm Black (Ink) + ,content_muted: "hsl(10, 15%, 35%)" // Dark Grey-Red + ,content_subtle: "hsl(10, 10%, 55%)" + ,content_inverse: "hsl(48, 50%, 90%)" + + // --- BRAND & ACTION (The Red Spectrum) --- + ,brand_primary: "hsl(12, 85%, 30%)" // H1 (Deep Oxblood) + ,brand_secondary: "hsl(10, 80%, 35%)" // H2 (Garnet) + ,brand_tertiary: "hsl(8, 70%, 40%)" // H3 (Brick) + ,brand_link: "hsl(12, 90%, 35%)" // Link + + // --- BORDERS --- + ,border_faint: "hsl(45, 30%, 80%)" + ,border_default: "hsl(45, 30%, 70%)" // Pencil Grey + ,border_strong: "hsl(12, 50%, 40%)" + + // --- STATE --- + ,state_success: "hsl(120, 40%, 30%)" + ,state_warning: "hsl(25, 90%, 45%)" + ,state_error: "hsl(0, 75%, 35%)" + ,state_info: "hsl(210, 60%, 40%)" + + // --- SYNTAX --- + ,syntax_keyword: "hsl(0, 75%, 35%)" + ,syntax_string: "hsl(100, 35%, 25%)" + ,syntax_func: "hsl(15, 85%, 35%)" + ,syntax_comment: "hsl(45, 20%, 50%)" + }; + + // --- APPLY THEME --- + const palette = RT.config.theme; + const body = document.body; + const html = document.documentElement; + + html.style.backgroundColor = palette.surface_0; + body.style.backgroundColor = palette.surface_0; + body.style.color = palette.content_main; + + const s = body.style; + for (const [key, value] of Object.entries(palette)) { + s.setProperty(`--rt-${key.replace(/_/g, '-')}`, value); + } + + // Global overrides + const style_id = 'rt-global-overrides'; + if (!document.getElementById(style_id)) { + const style = document.createElement('style'); + style.id = style_id; + style.textContent = ` + ::selection { background: var(--rt-surface-select); color: var(--rt-brand-primary); } + ::-moz-selection { background: var(--rt-surface-select); color: var(--rt-brand-primary); } + + ::-webkit-scrollbar { width: 12px; } + ::-webkit-scrollbar-track { background: var(--rt-surface-0); } + ::-webkit-scrollbar-thumb { + background: var(--rt-border-default); + border: 2px solid var(--rt-surface-0); + border-radius: 8px; + } + ::-webkit-scrollbar-thumb:hover { background: var(--rt-brand-secondary); } + + rt-article p, rt-article li { + text-shadow: 0px 0px 0.5px rgba(0,0,0, 0.2); + } + + .MathJax, .MathJax_Display, .mjx-chtml { + color: var(--rt-content-main) !important; + fill: var(--rt-content-main) !important; + stroke: var(--rt-content-main) !important; + } + `; + document.head.appendChild(style); + } + }; + +} )(); diff --git a/developer/authored/RT/core/body_visibility_hidden.js b/developer/authored/RT/core/body_visibility_hidden.js new file mode 100644 index 0000000..d6a178a --- /dev/null +++ b/developer/authored/RT/core/body_visibility_hidden.js @@ -0,0 +1,12 @@ +/* + Targets the root element to ensure total blackout during load. +*/ +function body_visibility_hidden(){ + const gate = document.createElement('style'); + gate.id = 'rt-visibility-gate'; + gate.textContent = 'html{visibility:hidden !important; background:black !important;}'; + document.head.appendChild(gate); +} + +window.StyleRT = window.StyleRT || {}; +window.StyleRT.body_visibility_hidden = body_visibility_hidden; diff --git a/developer/authored/RT/core/body_visibility_visible.js b/developer/authored/RT/core/body_visibility_visible.js new file mode 100644 index 0000000..ff1c4b6 --- /dev/null +++ b/developer/authored/RT/core/body_visibility_visible.js @@ -0,0 +1,13 @@ +/* + Restores visibility by removing the visibility gate. +*/ +function body_visibility_visible(){ + const gate = document.getElementById('rt-visibility-gate'); + if (gate){ + gate.remove(); + } + document.body.style.visibility = 'visible'; +} + +window.StyleRT = window.StyleRT || {}; +window.StyleRT.body_visibility_visible = body_visibility_visible; diff --git a/developer/authored/RT/core/loader.js b/developer/authored/RT/core/loader.js new file mode 100644 index 0000000..3f4ee68 --- /dev/null +++ b/developer/authored/RT/core/loader.js @@ -0,0 +1,27 @@ +window.StyleRT = window.StyleRT || {}; + +window.StyleRT.include = function(path_identifier){ + let parts_seq = path_identifier.split('/'); + let namespace = parts_seq[0]; + + let module_path = parts_seq.slice(1).join('/'); + + if(module_path === 'theme'){ + let saved_theme = localStorage.getItem('RT_theme_preference'); + if(!saved_theme){ + saved_theme = 'dark_gold'; + localStorage.setItem('RT_theme_preference' ,saved_theme); + } + module_path = 'theme/' + saved_theme; + } + + let base_path = window.StyleRT_namespaces[namespace]; + if(!base_path){ + console.error("Namespace not found: " + namespace); + return; + } + + let full_path = base_path + '/' + module_path + '.js'; + // FIXED: No backslashes in the closing script tag + document.write(''); +}; diff --git a/developer/authored/RT/core/utility.js b/developer/authored/RT/core/utility.js new file mode 100644 index 0000000..258eb28 --- /dev/null +++ b/developer/authored/RT/core/utility.js @@ -0,0 +1,109 @@ +/* + General utilities for the StyleRT library. +*/ + +window.StyleRT = window.StyleRT || {}; + +// --- DEBUG SYSTEM --- +window.StyleRT.debug = { + + // all debug messages enabled +/* + active_tokens: new Set([ + 'style', 'layout', 'pagination' + ,'selector', 'config', 'error' + ,'term' + ]), +*/ + active_tokens: new Set([ + 'term' + ]), + + log: function(token, message) { + if (this.active_tokens.has(token)) { + console.log(`[StyleRT:${token}]`, message); + } + }, + + warn: function(token, message) { + if (this.active_tokens.has(token)) { + console.warn(`[StyleRT:${token}]`, message); + } + }, + + // New: Always log errors regardless of token, but tag them + error: function(token, message) { + console.error(`[StyleRT:${token}] CRITICAL:`, message); + }, + + enable: function(token) { this.active_tokens.add(token); console.log(`Enabled: ${token}`); }, + disable: function(token) { this.active_tokens.delete(token); console.log(`Disabled: ${token}`); } +}; + +// --- UTILITIES --- +window.StyleRT.utility = { + // --- FONT PHYSICS --- + measure_ink_ratio: function(target_font, ref_font = null) { + const debug = window.StyleRT.debug; + debug.log('layout', `Measuring ink ratio for ${target_font}`); + + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + + if (!ref_font) { + const bodyStyle = window.getComputedStyle(document.body); + ref_font = bodyStyle.fontFamily; + } + + const get_metrics = (font) => { + ctx.font = '100px ' + font; + const metrics = ctx.measureText('M'); + return { + ascent: metrics.actualBoundingBoxAscent, + descent: metrics.actualBoundingBoxDescent + }; + }; + + const ref_m = get_metrics(ref_font); + const target_m = get_metrics(target_font); + + const ratio = ref_m.ascent / target_m.ascent; + // debug.log('layout', `Ink Ratio calculated: ${ratio.toFixed(3)}`); + + return { + ratio: ratio, + baseline_diff: ref_m.descent - target_m.descent + }; + }, + + // --- COLOR PHYSICS --- + is_color_light: function(color_string) { + const debug = window.StyleRT.debug; + + // 1. HSL Check + if (color_string.startsWith('hsl')) { + const numbers = color_string.match(/\d+/g); + if (numbers && numbers.length >= 3) { + const lightness = parseInt(numbers[2]); + return lightness > 50; + } + } + + // 2. RGB Check + const rgb = color_string.match(/\d+/g); + if (!rgb) { + // debug.warn('color_layout', `Failed to parse color: "${color_string}". Defaulting to Light.`); + return true; + } + + const r = parseInt(rgb[0]); + const g = parseInt(rgb[1]); + const b = parseInt(rgb[2]); + const luma = (r * 299 + g * 587 + b * 114) / 1000; + return luma > 128; + }, + + is_block_content: function(element) { + return element.textContent.trim().includes('\n'); + } +}; diff --git a/developer/authored/RT/element/TOC.js b/developer/authored/RT/element/TOC.js new file mode 100644 index 0000000..eb246f5 --- /dev/null +++ b/developer/authored/RT/element/TOC.js @@ -0,0 +1,102 @@ +/* + Processes tags. + Populates each with headings found below it. + + Attributes: + level="N" : Explicitly sets the target heading level (1-6). + e.g., level="1" collects H1s. level="2" collects H2s. + Stops collecting if it hits a heading of (level - 1) or higher. + + Default (No attribute): + Context Aware. Looks backwards for the nearest heading H(N). + Targets H(N+1). Stops at the next H(N). +*/ +window.StyleRT = window.StyleRT || {}; + +window.StyleRT.TOC = function(){ + const debug = window.StyleRT.debug || { log: function(){} }; + const TOC_seq = document.querySelectorAll('rt-toc'); + + TOC_seq.forEach( (container ,TOC_index) => { + container.style.display = 'block'; + + // 1. Determine Target Level + const attr_level = parseInt( container.getAttribute('level') ); + let target_level; + + if( !isNaN(attr_level) ){ + // EXPLICIT MODE + target_level = attr_level; + if(debug.log) debug.log('TOC' ,`TOC #${TOC_index} explicit target: H${target_level}`); + } else { + // IMPLICIT / CONTEXT MODE + let context_level = 0; // Default 0 (Root) + let prev = container.previousElementSibling; + while(prev){ + const match = prev.tagName.match(/^H([1-6])$/); + if(match){ + context_level = parseInt( match[1] ); + break; + } + prev = prev.previousElementSibling; + } + target_level = context_level + 1; + if(debug.log) debug.log('TOC' ,`TOC #${TOC_index} context implied target: H${target_level}`); + } + + // Stop condition: Stop if we hit a heading that is a "parent" or "sibling" of the context. + // Mathematically: Stop if found_level < target_level. + const stop_threshold = target_level; + + // 2. Setup Container + container.innerHTML = ''; + const title = document.createElement('h1'); + // Title logic: If targeting H1, the element serves as a Main TOC. Otherwise the element serves as a Section TOC. + title.textContent = target_level === 1 ? 'Table of Contents' : 'Section Contents'; + title.style.textAlign = 'center'; + container.appendChild(title); + + const list = document.createElement('ul'); + list.style.listStyle = 'none'; + list.style.paddingLeft = '0'; + container.appendChild(list); + + // 3. Scan Forward + let next_el = container.nextElementSibling; + while(next_el){ + const match = next_el.tagName.match(/^H([1-6])$/); + if(match){ + const found_level = parseInt( match[1] ); + + // STOP Logic: + // If we are looking for H2s, we stop if we hit an H1 (level 1). + // If we are looking for H1s, we stop if we hit nothing (level 0). + if(found_level < target_level){ + break; + } + + // COLLECT Logic: + if(found_level === target_level){ + if(!next_el.id) next_el.id = `TOC-ref-${TOC_index}-${found_level}-${list.children.length}`; + + const li = document.createElement('li'); + li.style.marginBottom = '0.5rem'; + + const a = document.createElement('a'); + a.href = `#${next_el.id}`; + a.textContent = next_el.textContent; + a.style.textDecoration = 'none'; + a.style.color = 'inherit'; + a.style.display = 'block'; + + a.onmouseover = () => a.style.color = 'var(--rt-brand-primary)'; + a.onmouseout = () => a.style.color = 'inherit'; + + li.appendChild(a); + list.appendChild(li); + } + } + next_el = next_el.nextElementSibling; + } + }); +}; diff --git a/developer/authored/RT/element/code.js b/developer/authored/RT/element/code.js new file mode 100644 index 0000000..7e6dca8 --- /dev/null +++ b/developer/authored/RT/element/code.js @@ -0,0 +1,129 @@ +/* + Processes tags. + Uses the central config or CSS variables from the theme. + + Removes common indent from lines of code. +*/ +function code() { + const RT = window.StyleRT; + const U = RT.utility; + const debug = RT.debug; + + debug.log('code', 'Starting render cycle.'); + + const metrics = U.measure_ink_ratio('monospace'); + + document.querySelectorAll('rt-code').forEach((el) => { + el.style.fontFamily = 'monospace'; + + const computed = window.getComputedStyle(el); + const accent = computed.getPropertyValue('--rt-accent').trim() || 'gold'; + + const is_block = U.is_block_content(el); + const parentColor = computed.color; + const is_text_light = U.is_color_light(parentColor); + + const alpha = is_block ? 0.08 : 0.15; + const overlay = is_text_light ? `rgba(255,255,255,${alpha})` : `rgba(0,0,0,${alpha})`; + const text_color = is_text_light ? '#ffffff' : '#000000'; + + el.style.backgroundColor = overlay; + + if (is_block) { + el.style.display = 'block'; + + // --- Tag-Relative Auto-Dedent Logic --- + + // 1. Get Tag Indentation (The Anchor) + let tagIndent = ''; + const prevNode = el.previousSibling; + if (prevNode && prevNode.nodeType === 3) { + const prevText = prevNode.nodeValue; + const lastNewLineIndex = prevText.lastIndexOf('\n'); + if (lastNewLineIndex !== -1) { + tagIndent = prevText.substring(lastNewLineIndex + 1); + } else if (/^\s*$/.test(prevText)) { + tagIndent = prevText; + } + } + + // 2. Calculate Common Leading Whitespace from Content + const rawLines = el.textContent.split('\n'); + + // Filter out empty lines for calculation purposes so they don't break the logic + const contentLines = rawLines.filter(line => line.trim().length > 0); + + let commonIndent = null; + + if (contentLines.length > 0) { + // Assume the first line sets the standard + const firstMatch = contentLines[0].match(/^\s*/); + commonIndent = firstMatch ? firstMatch[0] : ''; + + // Reduce the commonIndent if subsequent lines have LESS indentation + for (let i = 1; i < contentLines.length; i++) { + const line = contentLines[i]; + // Determine how much of commonIndent this line shares + let j = 0; + while (j < commonIndent.length && j < line.length && commonIndent[j] === line[j]) { + j++; + } + commonIndent = commonIndent.substring(0, j); + if (commonIndent.length === 0) break; // Optimization + } + } else { + commonIndent = ''; + } + + // 3. Process Content + // Rule: Only strip if the Common Indent contains the Tag Indent (Safety Check) + // This handles the Emacs case: Tag is " ", Common is " ". " " starts with " ". + // We strip " ", leaving the code flush left. + let finalString = ''; + + if (commonIndent.length > 0 && commonIndent.startsWith(tagIndent)) { + const cleanedLines = rawLines.map(line => { + // Strip the common indent from valid lines + return line.startsWith(commonIndent) ? line.replace(commonIndent, '') : line; + }); + + // Remove artifact lines (first/last empty lines) + if (cleanedLines.length > 0 && cleanedLines[0].length === 0) { + cleanedLines.shift(); + } + if (cleanedLines.length > 0 && cleanedLines[cleanedLines.length - 1].trim().length === 0) { + cleanedLines.pop(); + } + finalString = cleanedLines.join('\n'); + } else { + // Fallback: Code is to the left of the tag or weirdly formatted. + // Just trim the wrapper newlines. + finalString = el.textContent.trim(); + } + + el.textContent = finalString; + // --- End Indentation Logic --- + + el.style.whiteSpace = 'pre'; + el.style.fontSize = (parseFloat(computed.fontSize) * metrics.ratio * 0.95) + 'px'; + el.style.padding = '1.2rem'; + el.style.margin = '1.5rem 0'; + el.style.borderLeft = `4px solid ${accent}`; + el.style.color = 'inherit'; + } else { + el.style.display = 'inline'; + const exactPx = parseFloat(computed.fontSize) * metrics.ratio * 1.0; + el.style.fontSize = exactPx + 'px'; + el.style.padding = '0.1rem 0.35rem'; + el.style.borderRadius = '3px'; + const offsetPx = metrics.baseline_diff * (exactPx / 100); + el.style.verticalAlign = offsetPx + 'px'; + el.style.color = text_color; + } + }); + + debug.log('code', 'Render cycle complete.'); +} + +window.StyleRT = window.StyleRT || {}; +window.StyleRT.code = code; diff --git a/developer/authored/RT/element/math.js b/developer/authored/RT/element/math.js new file mode 100644 index 0000000..1e2ae8f --- /dev/null +++ b/developer/authored/RT/element/math.js @@ -0,0 +1,35 @@ +/* + Processes tags. + JavaScript: math() + HTML Tag: (parsed as rt-math) +*/ +function math(){ + // querySelector treats 'rt-math' as case-insensitive for the tag + document.querySelectorAll('rt-math').forEach(el => { + if (el.textContent.startsWith('$')) return; + + const is_block = el.parentElement.tagName === 'DIV' || + el.textContent.includes('\n') || + el.parentElement.childNodes.length === 1; + + const delimiter = is_block ? '$$' : '$'; + el.style.display = is_block ? 'block' : 'inline'; + el.textContent = `${delimiter}${el.textContent.trim()}${delimiter}`; + }); + + // MathJax must find its config at window.MathJax + window.MathJax = { + tex: { + inlineMath: [['$', '$']], + displayMath: [['$$', '$$']] + } + }; + + const script = document.createElement('script'); + script.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js'; + script.async = true; + document.head.appendChild(script); +} + +window.StyleRT = window.StyleRT || {}; +window.StyleRT.math = math; diff --git a/developer/authored/RT/element/term.js b/developer/authored/RT/element/term.js new file mode 100644 index 0000000..4ebc634 --- /dev/null +++ b/developer/authored/RT/element/term.js @@ -0,0 +1,107 @@ +/* + Processes and tags. + - Styles only the first occurrence of a unique term/neologism. + - The "-em" variants (e.g., ) are always styled. + - Automatically generates IDs for first occurrences for future indexing. +*/ + +window.StyleRT = window.StyleRT || {}; + +window.StyleRT.term = function() { + const RT = window.StyleRT; + + const debug = RT.debug || { + log: function() {} + ,warn: function() {} + ,error: function() {} + }; + + const DEBUG_TOKEN_S = 'term'; + + try { + // Track seen terms so only the first occurrence is decorated + const seen_terms_dpa = new Set(); + + const apply_style = (el, is_neologism_b) => { + el.style.fontStyle = 'italic'; + el.style.fontWeight = is_neologism_b ? '600' : '500'; + el.style.color = is_neologism_b + ? 'var(--rt-brand-secondary)' + : 'var(--rt-brand-primary)'; + el.style.paddingRight = '0.1em'; // Compensation for italic slant + el.style.display = 'inline'; + }; + + const clear_style = (el) => { + el.style.fontStyle = 'normal'; + el.style.color = 'inherit'; + el.style.fontWeight = 'inherit'; + el.style.paddingRight = ''; + el.style.display = ''; + }; + + const selector_s = [ + 'rt-term' + ,'rt-term-em' + ,'rt-neologism' + ,'rt-neologism-em' + ].join(','); + + const tags_dpa = document.querySelectorAll(selector_s); + + debug.log(DEBUG_TOKEN_S, `Scanning ${tags_dpa.length} term tags`); + + tags_dpa.forEach(el => { + const tag_name_s = el.tagName.toLowerCase(); + const is_neologism_b = tag_name_s.includes('neologism'); + const is_explicit_em_b = tag_name_s.endsWith('-em'); + + const term_text_raw_s = (el.textContent || '').trim(); + if (!term_text_raw_s.length) { + debug.warn(DEBUG_TOKEN_S, `Empty term tag encountered: <${tag_name_s}>`); + return; + } + + // Normalize text for uniqueness tracking + const term_norm_s = term_text_raw_s.toLowerCase(); + + // Slug for ID generation (simple + stable) + const slug_s = term_norm_s.replace(/\s+/g, '-'); + + const is_first_occurrence_b = !seen_terms_dpa.has(term_norm_s); + + if (is_explicit_em_b || is_first_occurrence_b) { + apply_style(el, is_neologism_b); + + if (!is_explicit_em_b && is_first_occurrence_b) { + seen_terms_dpa.add(term_norm_s); + + if (!el.id) { + el.id = `def-${is_neologism_b ? 'neo-' : ''}${slug_s}`; + debug.log( + DEBUG_TOKEN_S + ,`First occurrence: "${term_norm_s}" -> id="${el.id}"` + ); + } else { + debug.log( + DEBUG_TOKEN_S + ,`First occurrence: "${term_norm_s}" (existing id="${el.id}")` + ); + } + } else if (is_explicit_em_b) { + debug.log( + DEBUG_TOKEN_S + ,`Emphasized occurrence: "${term_norm_s}" (<${tag_name_s}>)` + ); + } + } else { + // Subsequent mentions render as normal prose + clear_style(el); + } + }); + + debug.log(DEBUG_TOKEN_S, `Unique terms defined: ${seen_terms_dpa.size}`); + } catch (e) { + debug.error('error', `term failed: ${e && e.message ? e.message : String(e)}`); + } +}; diff --git a/developer/authored/RT/element/theme_selector.js b/developer/authored/RT/element/theme_selector.js new file mode 100644 index 0000000..05759d2 --- /dev/null +++ b/developer/authored/RT/element/theme_selector.js @@ -0,0 +1,27 @@ +class ThemeSelector extends HTMLElement{ + connectedCallback(){ + let current_theme = localStorage.getItem('RT_theme_preference'); + if(!current_theme){ + current_theme = 'dark_gold'; + } + + this.innerHTML = ` +
+ Theme Selection
+
+ +
+ `; + + this.addEventListener( 'change' ,(e) => { + localStorage.setItem('RT_theme_preference' ,e.target.value); + location.reload(); + } ); + } +} + +customElements.define('rt-theme-selector' ,ThemeSelector); diff --git a/developer/authored/RT/element/title.js b/developer/authored/RT/element/title.js new file mode 100644 index 0000000..fefd991 --- /dev/null +++ b/developer/authored/RT/element/title.js @@ -0,0 +1,60 @@ +/* + Processes tags. + Generates a standard document header block. + + Usage: + +*/ +window.StyleRT = window.StyleRT || {}; + +window.StyleRT.title = function() { + const debug = window.StyleRT.debug || { log: function(){} }; + + document.querySelectorAll('rt-title').forEach(el => { + const title = el.getAttribute('title') || 'Untitled Document'; + const author = el.getAttribute('author'); + const date = el.getAttribute('date'); + + if (debug.log) debug.log('title', `Generating title block: ${title}`); + + // Container + const container = document.createElement('div'); + container.style.textAlign = 'center'; + container.style.marginBottom = '3rem'; + container.style.marginTop = '2rem'; + container.style.borderBottom = '1px solid var(--rt-border-default)'; + container.style.paddingBottom = '1.5rem'; + + // Main Title (H1) + const h1 = document.createElement('h1'); + h1.textContent = title; + h1.style.margin = '0 0 0.8rem 0'; + h1.style.border = 'none'; // Override standard H1 border + h1.style.padding = '0'; + h1.style.color = 'var(--rt-brand-primary)'; + h1.style.fontSize = '2.5em'; + h1.style.lineHeight = '1.1'; + h1.style.letterSpacing = '-0.03em'; + + container.appendChild(h1); + + // Metadata Row (Author | Date) + if (author || date) { + const meta = document.createElement('div'); + meta.style.color = 'var(--rt-content-muted)'; + meta.style.fontStyle = 'italic'; + meta.style.fontSize = '1.1em'; + meta.style.fontFamily = '"Georgia", "Times New Roman", serif'; // Classy serif + + const parts = []; + if (author) parts.push(`${author}`); + if (date) parts.push(date); + + meta.innerHTML = parts.join('  —  '); + container.appendChild(meta); + } + + // Replace the raw tag with the generated block + el.replaceWith(container); + }); +}; diff --git a/developer/authored/RT/layout/article_tech_ref.js b/developer/authored/RT/layout/article_tech_ref.js new file mode 100644 index 0000000..795e2e1 --- /dev/null +++ b/developer/authored/RT/layout/article_tech_ref.js @@ -0,0 +1,84 @@ +(function(){ + const RT = window.StyleRT = window.StyleRT || {}; + + // 1. Declare Dependencies with updated core/ and layout/ paths + RT.include('RT/core/utility'); + RT.include('RT/element/math'); + RT.include('RT/element/code'); + RT.include('RT/element/term'); + RT.include('RT/element/TOC'); + RT.include('RT/element/title'); + RT.include('RT/element/theme_selector'); + RT.include('RT/layout/paginate_by_element'); + RT.include('RT/layout/page_fixed_glow'); + RT.include('RT/core/body_visibility_visible'); + + // 2. The Typography Layout (Adapted from the original) + RT.article = function(){ + RT.config = RT.config || {}; + RT.config.article = { + font_family: '"Noto Sans", "Segoe UI", "Helvetica Neue", sans-serif' + ,line_height: "1.8" + ,font_size: "16px" + ,font_weight: "400" + ,max_width: "820px" + ,margin: "0 auto" + }; + + if( RT.config.theme && RT.config.theme.meta_is_dark === false ){ + RT.config.article.font_weight = "600"; + } + + const conf = RT.config.article; + const article_seq = document.querySelectorAll("RT-article"); + + if(article_seq.length === 0) return; + + for(let i = 0; i < article_seq.length; i++){ + let style = article_seq[i].style; + style.display = "block"; + style.fontFamily = conf.font_family; + style.fontSize = conf.font_size; + style.lineHeight = conf.line_height; + style.fontWeight = conf.font_weight; + style.maxWidth = conf.max_width; + style.margin = conf.margin; + style.padding = "0 20px"; + style.color = "var(--rt-content-main)"; + } + + // CSS injection for headers, lists, etc., goes here + // (Omitted for brevity, but carried over from the original file) + }; + + // 3. The Execution Sequence + const run_semantics = function(){ + if(RT.theme) RT.theme(); + RT.article(); + + // Call the newly renamed element functions + if(RT.title) RT.title(); + if(RT.term) RT.term(); + if(RT.math) RT.math(); + if(RT.code) RT.code(); + + // Check for MathJax typesetting + if(window.MathJax && MathJax.Hub && MathJax.Hub.Queue){ + MathJax.Hub.Queue( ["Typeset" ,MathJax.Hub] ,run_layout ); + }else{ + run_layout(); + } + }; + + const run_layout = function(){ + if(RT.TOC) RT.TOC(); + if(RT.paginate_by_element) RT.paginate_by_element(); + if(RT.page) RT.page(); + if(RT.body_visibility_visible) RT.body_visibility_visible(); + }; + + // 4. Bind to DOM Ready + // This replaces the need to put a script at the bottom of the HTML body + document.addEventListener( 'DOMContentLoaded' ,run_semantics ); + +})(); diff --git a/developer/authored/RT/layout/memo_State.js b/developer/authored/RT/layout/memo_State.js new file mode 100644 index 0000000..a2eb279 --- /dev/null +++ b/developer/authored/RT/layout/memo_State.js @@ -0,0 +1,70 @@ +/* + + + + + + + +*/ + +(function(){ + const RT = window.StyleRT = window.StyleRT || {}; + + // 1. Declare Dependencies + RT.include('RT/core/utility'); + RT.include('RT/element/title'); + RT.include('RT/element/term'); + RT.include('RT/element/TOC'); + RT.include('RT/core/body_visibility_visible'); + + // 2. The Typography Layout + RT.memo_state_dept = function(){ + const body = document.body; + const html = document.documentElement; + + // Force strict print colors regardless of user system settings + html.style.backgroundColor = "white"; + body.style.backgroundColor = "white"; + body.style.color = "black"; + + // Target the new semantic tag + const memo_seq = document.querySelectorAll("RT-memo"); + if(memo_seq.length === 0) return; + + for(let i = 0; i < memo_seq.length; i++){ + let style = memo_seq[i].style; + style.display = "block"; + style.fontFamily = '"Times New Roman", Times, serif'; + style.fontSize = "12pt"; + style.lineHeight = "1.15"; + // 8.5 inch standard width minus 1-inch margins on each side + style.maxWidth = "6.5in"; + style.margin = "1in auto"; + style.padding = "0"; + style.textAlign = "left"; + style.color = "black"; + } + }; + + // 3. The Execution Sequence + const run_semantics = function(){ + RT.memo_state_dept(); + + if(RT.title) RT.title(); + if(RT.term) RT.term(); + if(RT.TOC) RT.TOC(); + + run_layout(); + }; + + const run_layout = function(){ + if(RT.body_visibility_visible) RT.body_visibility_visible(); + }; + + // 4. Bind to DOM Ready + document.addEventListener('DOMContentLoaded' ,run_semantics); + +})(); diff --git a/developer/authored/RT/layout/page_fixed_glow.js b/developer/authored/RT/layout/page_fixed_glow.js new file mode 100644 index 0000000..91bd648 --- /dev/null +++ b/developer/authored/RT/layout/page_fixed_glow.js @@ -0,0 +1,88 @@ +/* + Page Layout: Fixed Glow + Standard: Theme 1.0 + Description: A fixed-height container with a glowing border effect that matches the active theme. +*/ +(function(){ + const RT = window.StyleRT = window.StyleRT || {}; + + // Function name stays generic so the orchestrator can call RT.page() regardless of file choice + RT.page = function() { + RT.config = RT.config || {}; + + // Default Configuration + // We use CSS Variables here so the Theme controls the actual colors. + const defaults = { + width: "100%" + ,height: "1056px" + ,padding: "3rem" + ,margin: "4rem auto" + + // Dynamic Theme Bindings + ,bg_color: "var(--rt-surface-0)" // Black (Dark) or Cream (Light) + ,border_color: "var(--rt-brand-primary)" // The Main Accent Color + ,text_color: "var(--rt-brand-primary)" // Page Number Color + + // The Glow: Uses the primary brand color for the shadow + ,shadow: "drop-shadow(0px 0px 15px var(--rt-brand-primary))" + }; + + // Merge defaults + RT.config.page = Object.assign({}, defaults, RT.config.page || {}); + + const conf = RT.config.page; + const style_id = 'rt-page-fixed-glow'; + + if (!document.getElementById(style_id)) { + const style_el = document.createElement('style'); + style_el.id = style_id; + + style_el.textContent = ` + /* Reset page counter on the article container */ + rt-article { + counter-reset: rt-page-counter; + } + + rt-page { + display: block; + position: relative; + box-sizing: border-box; + overflow: hidden; + + /* Dimensions */ + width: ${conf.width}; + height: ${conf.height}; + margin: ${conf.margin}; + padding: ${conf.padding}; + + /* Theming */ + background-color: ${conf.bg_color}; + border: 1px solid ${conf.border_color}; + + /* The "Glow" Effect */ + filter: ${conf.shadow}; + + /* Counter Increment */ + counter-increment: rt-page-counter; + } + + /* Page Numbering */ + rt-page::after { + content: "Page " counter(rt-page-counter); + position: absolute; + bottom: 1.5rem; + right: 3rem; + + font-family: "Noto Sans", sans-serif; + font-size: 0.9rem; + font-weight: bold; + + color: ${conf.text_color}; + opacity: 0.8; + pointer-events: none; /* Prevent interference with clicks */ + } + `; + document.head.appendChild(style_el); + } + }; +})(); diff --git a/developer/authored/RT/layout/paginate_by_element.js b/developer/authored/RT/layout/paginate_by_element.js new file mode 100644 index 0000000..e085148 --- /dev/null +++ b/developer/authored/RT/layout/paginate_by_element.js @@ -0,0 +1,72 @@ +/* + Layout Paginator: paginate_by_element +*/ +window.StyleRT = window.StyleRT || {}; + +window.StyleRT.paginate_by_element = function() { + const RT = window.StyleRT; + + // Fix: Read safely without overwriting the config namespace + const page_conf = (RT.config && RT.config.page) ? RT.config.page : {}; + const page_height_limit = page_conf.height_limit || 1000; + + const article_seq = document.querySelectorAll("RT-article"); + + // HURDLE: Error if no articles found to paginate + if(article_seq.length === 0) { + RT.debug.error('pagination', 'No elements found. Pagination aborted.'); + return; + } + + article_seq.forEach( (article) => { + const raw_elements = Array.from(article.children).filter(el => + !['SCRIPT', 'STYLE', 'RT-PAGE'].includes(el.tagName) + ); + + if(raw_elements.length === 0) return; + + const pages = []; + let current_batch = []; + let current_h = 0; + + const get_el_height = (el) => { + const rect = el.getBoundingClientRect(); + const style = window.getComputedStyle(el); + const margin = parseFloat(style.marginTop) + parseFloat(style.marginBottom); + return (rect.height || 0) + (margin || 0); + }; + + for (let i = 0; i < raw_elements.length; i++) { + const el = raw_elements[i]; + const h = get_el_height(el); + const is_heading = /^H[1-6]/.test(el.tagName); + + let total_required_h = h; + if (is_heading && i + 1 < raw_elements.length) { + total_required_h += get_el_height(raw_elements[i + 1]); + } + + if (current_h + total_required_h > page_height_limit && current_batch.length > 0) { + pages.push(current_batch); + current_batch = []; + current_h = 0; + } + + current_batch.push(el); + current_h += h; + } + + if (current_batch.length > 0) pages.push(current_batch); + + article.innerHTML = ''; + + pages.forEach( (list, index) => { + const page_el = document.createElement('rt-page'); + page_el.id = `page-${index+1}`; + list.forEach(item => page_el.appendChild(item)); + article.appendChild(page_el); + }); + + if (RT.debug) RT.debug.log('pagination', `Article paginated into ${pages.length} pages.`); + }); +}; diff --git a/developer/authored/RT/theme/dark_gold.js b/developer/authored/RT/theme/dark_gold.js new file mode 100644 index 0000000..f576147 --- /dev/null +++ b/developer/authored/RT/theme/dark_gold.js @@ -0,0 +1,94 @@ +/* + Theme: Inverse Wheat (Dark) + Standard: Theme 1.0 + Description: High contrast Amber on Deep Charcoal. +*/ +( function(){ + const RT = window.StyleRT = window.StyleRT || {}; + + RT.theme = function(){ + RT.config = RT.config || {}; + + // THEME 1.0 DATA CONTRACT + RT.config.theme = { + meta_is_dark: true + ,meta_name: "Inverse Wheat" + + // --- SURFACES (Depth & Container Hierarchy) --- + ,surface_0: "hsl(0, 0%, 5%)" // App Background (Deepest) + ,surface_1: "hsl(0, 0%, 10%)" // Sidebar / Nav / Panels + ,surface_2: "hsl(0, 0%, 14%)" // Cards / Floating Elements + ,surface_3: "hsl(0, 0%, 18%)" // Modals / Dropdowns / Popovers + ,surface_input: "hsl(0, 0%, 12%)" // Form Inputs + ,surface_code: "hsl(0, 0%, 11%)" // Code Block Background + ,surface_select: "hsl(45, 100%, 15%)" // Text Selection Highlight + + // --- CONTENT (Text & Icons) --- + ,content_main: "hsl(50, 60%, 85%)" // Primary Reading Text + ,content_muted: "hsl(36, 15%, 60%)" // Metadata, subtitles + ,content_subtle: "hsl(36, 10%, 40%)" // Placeholders, disabled states + ,content_inverse: "hsl(0, 0%, 5%)" // Text on high-contrast buttons + + // --- BRAND & ACTION (The "Wheat" Identity) --- + ,brand_primary: "hsl(45, 100%, 50%)" // Main Action / H1 / Focus Ring + ,brand_secondary: "hsl(38, 90%, 65%)" // Secondary Buttons / H2 + ,brand_tertiary: "hsl(30, 60%, 70%)" // Accents / H3 + ,brand_link: "hsl(48, 100%, 50%)" // Hyperlinks (High Visibility) + + // --- BORDERS & DIVIDERS --- + ,border_faint: "hsl(36, 20%, 15%)" // Subtle separation + ,border_default: "hsl(36, 20%, 25%)" // Standard Card Borders + ,border_strong: "hsl(36, 20%, 40%)" // Active states / Inputs + + // --- STATE & FEEDBACK (Earth Tones) --- + ,state_success: "hsl(100, 50%, 45%)" // Olive Green + ,state_warning: "hsl(35, 90%, 55%)" // Burnt Orange + ,state_error: "hsl(0, 60%, 55%)" // Brick Red + ,state_info: "hsl(200, 40%, 55%)" // Slate Blue + + // --- SYNTAX HIGHLIGHTING (For Code) --- + ,syntax_keyword: "hsl(35, 100%, 65%)" // Orange + ,syntax_string: "hsl(75, 50%, 60%)" // Sage Green + ,syntax_func: "hsl(45, 90%, 70%)" // Light Gold + ,syntax_comment: "hsl(36, 15%, 45%)" // Brown/Gray + }; + + // --- APPLY THEME --- + const palette = RT.config.theme; + const body = document.body; + const html = document.documentElement; + + // 1. Paint Base + html.style.backgroundColor = palette.surface_0; + body.style.backgroundColor = palette.surface_0; + body.style.color = palette.content_main; + + // 2. Export Variables (Standardization) + const s = body.style; + for (const [key, value] of Object.entries(palette)) { + s.setProperty(`--rt-${key.replace(/_/g, '-')}`, value); + } + + // 3. Global Overrides + const style_id = 'rt-global-overrides'; + if (!document.getElementById(style_id)) { + const style = document.createElement('style'); + style.id = style_id; + style.textContent = ` + ::selection { background: var(--rt-surface-select); color: var(--rt-brand-primary); } + ::-moz-selection { background: var(--rt-surface-select); color: var(--rt-brand-primary); } + + ::-webkit-scrollbar { width: 12px; } + ::-webkit-scrollbar-track { background: var(--rt-surface-0); } + ::-webkit-scrollbar-thumb { + background: var(--rt-border-default); + border: 2px solid var(--rt-surface-0); + border-radius: 8px; + } + ::-webkit-scrollbar-thumb:hover { background: var(--rt-brand-secondary); } + `; + document.head.appendChild(style); + } + }; + +} )(); diff --git a/developer/authored/RT/theme/light.js b/developer/authored/RT/theme/light.js new file mode 100644 index 0000000..4b5eea4 --- /dev/null +++ b/developer/authored/RT/theme/light.js @@ -0,0 +1,70 @@ +/* + Theme: Classic Wheat (Light) + Standard: Theme 1.0 + Description: Warm paper tones with Burnt Orange accents. +*/ +( function(){ + const RT = window.StyleRT = window.StyleRT || {}; + + RT.theme_light = function(){ + RT.config = RT.config || {}; + + // THEME 1.0 DATA CONTRACT + RT.config.theme = { + meta_is_dark: false + ,meta_name: "Classic Wheat" + + // --- SURFACES --- + ,surface_0: "hsl(40, 30%, 94%)" // App Background (Cream/Linen) + ,surface_1: "hsl(40, 25%, 90%)" // Sidebar (Slightly darker beige) + ,surface_2: "hsl(40, 20%, 98%)" // Cards (Lighter, almost white) + ,surface_3: "hsl(0, 0%, 100%)" // Modals (Pure White) + ,surface_input: "hsl(40, 20%, 98%)" // Form Inputs + ,surface_code: "hsl(40, 15%, 90%)" // Code Block Background + ,surface_select: "hsl(45, 100%, 85%)" // Text Selection Highlight + + // --- CONTENT --- + ,content_main: "hsl(30, 20%, 20%)" // Deep Umber (Not Black) + ,content_muted: "hsl(30, 15%, 45%)" // Medium Brown + ,content_subtle: "hsl(30, 10%, 65%)" // Light Brown/Gray + ,content_inverse: "hsl(40, 30%, 94%)" // Text on dark buttons + + // --- BRAND & ACTION --- + ,brand_primary: "hsl(30, 90%, 35%)" // Burnt Orange (Action) + ,brand_secondary: "hsl(35, 70%, 45%)" // Rust / Gold + ,brand_tertiary: "hsl(25, 60%, 55%)" // Copper + ,brand_link: "hsl(30, 100%, 35%)" // Link Color + + // --- BORDERS --- + ,border_faint: "hsl(35, 20%, 85%)" + ,border_default: "hsl(35, 20%, 75%)" + ,border_strong: "hsl(35, 20%, 55%)" + + // --- STATE & FEEDBACK --- + ,state_success: "hsl(100, 40%, 40%)" // Forest Green + ,state_warning: "hsl(30, 90%, 50%)" // Persimmon + ,state_error: "hsl(0, 60%, 45%)" // Crimson + ,state_info: "hsl(200, 50%, 45%)" // Navy Blue + + // --- SYNTAX --- + ,syntax_keyword: "hsl(20, 90%, 45%)" // Rust + ,syntax_string: "hsl(100, 35%, 35%)" // Ivy Green + ,syntax_func: "hsl(300, 30%, 40%)" // Muted Purple + ,syntax_comment: "hsl(35, 10%, 60%)" // Light Brown + }; + + // --- APPLY THEME --- + const palette = RT.config.theme; + const body = document.body; + const html = document.documentElement; + + html.style.backgroundColor = palette.surface_0; + body.style.backgroundColor = palette.surface_0; + body.style.color = palette.content_main; + + const s = body.style; + for (const [key, value] of Object.entries(palette)) { + s.setProperty(`--rt-${key.replace(/_/g, '-')}`, value); + } + }; +} )(); diff --git a/developer/authored/RT/theme/light_gold.js b/developer/authored/RT/theme/light_gold.js new file mode 100644 index 0000000..206f3da --- /dev/null +++ b/developer/authored/RT/theme/light_gold.js @@ -0,0 +1,103 @@ +/* + Theme: Golden Wheat (Light) - "Spanish Gold Edition" + File: style/theme-light-gold.js + Standard: Theme 1.0 + Description: Light Parchment background with Oxblood Red ink. +*/ +( function(){ + const RT = window.StyleRT = window.StyleRT || {}; + + RT.theme = function(){ + RT.config = RT.config || {}; + + RT.config.theme = { + meta_is_dark: false + ,meta_name: "Golden Wheat (Yellow)" + + // --- SURFACES (Light Parchment) --- + // Shifted lightness up to 94% for a "whiter" feel that still holds the yellow tint. + ,surface_0: "hsl(48, 50%, 94%)" // Main Page: Fine Parchment + ,surface_1: "hsl(48, 40%, 90%)" // Panels: Slightly darker + ,surface_2: "hsl(48, 30%, 97%)" // Cards: Very light + ,surface_3: "hsl(0, 0%, 100%)" // Popups + ,surface_input: "hsl(48, 20%, 96%)" + ,surface_code: "hsl(48, 25%, 88%)" // Distinct Code BG + ,surface_select: "hsl(10, 70%, 85%)" // Red Highlight + + // --- CONTENT (Deep Ink) --- + ,content_main: "hsl(10, 25%, 7%)" // Deep Warm Black (Ink) + ,content_muted: "hsl(10, 15%, 35%)" // Dark Grey-Red + ,content_subtle: "hsl(10, 10%, 55%)" + ,content_inverse: "hsl(48, 50%, 90%)" + + // --- BRAND & ACTION (The Red Spectrum) --- + ,brand_primary: "hsl(12, 85%, 30%)" // H1 (Deep Oxblood) + ,brand_secondary: "hsl(10, 80%, 35%)" // H2 (Garnet) + ,brand_tertiary: "hsl(8, 70%, 40%)" // H3 (Brick) + ,brand_link: "hsl(12, 90%, 35%)" // Link + + // --- BORDERS --- + ,border_faint: "hsl(45, 30%, 80%)" + ,border_default: "hsl(45, 30%, 70%)" // Pencil Grey + ,border_strong: "hsl(12, 50%, 40%)" + + // --- STATE --- + ,state_success: "hsl(120, 40%, 30%)" + ,state_warning: "hsl(25, 90%, 45%)" + ,state_error: "hsl(0, 75%, 35%)" + ,state_info: "hsl(210, 60%, 40%)" + + // --- SYNTAX --- + ,syntax_keyword: "hsl(0, 75%, 35%)" + ,syntax_string: "hsl(100, 35%, 25%)" + ,syntax_func: "hsl(15, 85%, 35%)" + ,syntax_comment: "hsl(45, 20%, 50%)" + }; + + // --- APPLY THEME --- + const palette = RT.config.theme; + const body = document.body; + const html = document.documentElement; + + html.style.backgroundColor = palette.surface_0; + body.style.backgroundColor = palette.surface_0; + body.style.color = palette.content_main; + + const s = body.style; + for (const [key, value] of Object.entries(palette)) { + s.setProperty(`--rt-${key.replace(/_/g, '-')}`, value); + } + + // Global overrides + const style_id = 'rt-global-overrides'; + if (!document.getElementById(style_id)) { + const style = document.createElement('style'); + style.id = style_id; + style.textContent = ` + ::selection { background: var(--rt-surface-select); color: var(--rt-brand-primary); } + ::-moz-selection { background: var(--rt-surface-select); color: var(--rt-brand-primary); } + + ::-webkit-scrollbar { width: 12px; } + ::-webkit-scrollbar-track { background: var(--rt-surface-0); } + ::-webkit-scrollbar-thumb { + background: var(--rt-border-default); + border: 2px solid var(--rt-surface-0); + border-radius: 8px; + } + ::-webkit-scrollbar-thumb:hover { background: var(--rt-brand-secondary); } + + rt-article p, rt-article li { + text-shadow: 0px 0px 0.5px rgba(0,0,0, 0.2); + } + + .MathJax, .MathJax_Display, .mjx-chtml { + color: var(--rt-content-main) !important; + fill: var(--rt-content-main) !important; + stroke: var(--rt-content-main) !important; + } + `; + document.head.appendChild(style); + } + }; + +} )(); diff --git a/developer/authored/hello.cli.c b/developer/authored/hello.cli.c deleted file mode 100644 index a626cac..0000000 --- a/developer/authored/hello.cli.c +++ /dev/null @@ -1,2 +0,0 @@ -#include -int main(void){ puts("hello from Rabbit CLI"); return 0; } diff --git a/developer/tool/do_all b/developer/tool/do_all index 2495705..2350fd8 100755 --- a/developer/tool/do_all +++ b/developer/tool/do_all @@ -4,8 +4,8 @@ script_afp=$(realpath "${BASH_SOURCE[0]}") # input guards setup_must_be="developer/tool/setup" - if [ "$ENV" != "$env_must_be" ]; then - echo "$(script_fp):: error: must be run in the $env_must_be environment" + if [ "$SETUP" != "$setup_must_be" ]; then + echo "$(script_fp):: error: must be run in the $setup_must_be environment" exit 1 fi diff --git a/developer/tool/make b/developer/tool/make index 58636de..42babce 100755 --- a/developer/tool/make +++ b/developer/tool/make @@ -4,8 +4,8 @@ script_afp=$(realpath "${BASH_SOURCE[0]}") # input guards setup_must_be="developer/tool/setup" - if [ "$ENV" != "$env_must_be" ]; then - echo "$(script_fp):: error: must be run in the $env_must_be environment" + if [ "$SETUP" != "$setup_must_be" ]; then + echo "$(script_fp):: error: must be run in the $setup_must_be environment" exit 1 fi @@ -15,12 +15,7 @@ set -x cd "$REPO_HOME"/developer || exit 1 # /bin/make -f tool/makefile $@ - mkdir -p scratchpad/authored - cp authored/HTTP_server.js scratchpad/authored/ - cp authored/port_forwarder scratchpad/authored/ - cp authored/port_forwarder_CLI scratchpad/authored/ - cp authored/permission_RT scratchpad/authored/ - cp authored/permission_UPG scratchpad/authored/ + mkdir -p scratchpad/stage && cp -au authored/RT scratchpad/stage/ set +x echo "$(script_fn) done." diff --git a/developer/tool/release b/developer/tool/release index 2b1027a..24f260c 100755 --- a/developer/tool/release +++ b/developer/tool/release @@ -4,10 +4,10 @@ import os ,sys ,shutil ,stat ,pwd ,grp ,glob ,tempfile HELP = """usage: release {write|clean|ls|diff|help|dry write} - write Writes promoted files from scratchpad/stage into user/release. Only updates newer files. - clean Remove all contents of the user/release directory. - ls List user/release as an indented tree: PERMS OWNER NAME. - diff List files in user/release that are not in scratchpad/stage, or are newer. + write Writes promoted files from scratchpad/stage into consumer/release. Only updates newer files. + clean Remove all contents of the consumer/release directory. + ls List consumer/release as an indented tree: PERMS OWNER NAME. + diff List files in consumer/release that are not in scratchpad/stage, or are newer. help Show this message. dry write Preview what write would do without modifying the filesystem. """ @@ -20,15 +20,15 @@ def exit_with_status(msg ,code=1): print(f"release: {msg}" ,file=sys.stderr) sys.exit(code) -def assert_env(): - env = os.environ.get("ENV" ,"") - if(env != ENV_MUST_BE): +def assert_setup(): + setup_val = os.environ.get("SETUP" ,"") + if(setup_val != SETUP_MUST_BE): hint = ( "SETUP is not 'developer/tool/setup'.\n" "Enter the project with: . setup developer\n" "That script exports: ROLE=developer; SETUP=$ROLE/tool/setup" ) - exit_with_status(f"bad environment: ENV='{env}'. {hint}") + exit_with_status(f"bad environment: SETUP='{setup_val}'. {hint}") def repo_home(): rh = os.environ.get("REPO_HOME") @@ -43,10 +43,10 @@ def dpath(*parts): ,*parts ) -def upath(*parts): +def cpath(*parts): return os.path.join( repo_home() - ,"user" + ,"consumer" ,"release" ,*parts ) @@ -54,8 +54,8 @@ def upath(*parts): def dev_root(): return dpath() -def user_root(): - return upath() +def consumer_root(): + return cpath() def _display_src(p_abs: str) -> str: try: @@ -69,10 +69,10 @@ def _display_dst(p_abs: str) -> str: try: rel = os.path.relpath( p_abs - ,user_root() + ,consumer_root() ) rel = "" if rel == "." else rel - return "$REPO_HOME/user/release" + ("/" + rel if rel else "") + return "$REPO_HOME/consumer/release" + ("/" + rel if rel else "") except Exception: return p_abs @@ -83,7 +83,7 @@ def ensure_mode(path_str ,mode): def ensure_dir(path_str ,mode=DEFAULT_DIR_MODE ,dry=False): if(dry): if( not os.path.isdir(path_str) ): - shown = _display_dst(path_str) if path_str.startswith(user_root()) else ( + shown = _display_dst(path_str) if path_str.startswith(consumer_root()) else ( os.path.relpath(path_str ,dev_root()) if path_str.startswith(dev_root()) else path_str ) print(f"(dry) mkdir -m {oct(mode)[2:]} '{shown}'") @@ -141,7 +141,7 @@ def list_tree(root_dp): if( len(ownergrp) > ogw ): ogw = len(ownergrp) - print("user/release/") + print("consumer/release/") for isdir ,depth ,perms ,ownergrp ,name in entries: indent = " " * depth print(f"{perms} {ownergrp:<{ogw}} {indent}{name}") @@ -175,8 +175,8 @@ def copy_one(src_abs ,dst_abs ,mode ,dry=False): print(f"+ install -m {oct(mode)[2:]} '{src_show}' '{dst_show}'") def cmd_write(dry=False): - assert_env() - ensure_dir(upath() ,DEFAULT_DIR_MODE ,dry=dry) + assert_setup() + ensure_dir(cpath() ,DEFAULT_DIR_MODE ,dry=dry) src_root = dpath( "scratchpad" @@ -192,7 +192,7 @@ def cmd_write(dry=False): for fn in files: src_abs = os.path.join(root ,fn) rel = os.path.relpath(src_abs ,src_root) - dst_abs = os.path.join(upath() ,rel) + dst_abs = os.path.join(cpath() ,rel) if( os.path.exists(dst_abs) ): src_mtime = os.stat(src_abs).st_mtime @@ -216,8 +216,8 @@ def cmd_write(dry=False): print(f"(info) nothing new to promote from {_display_src(src_root)}") def cmd_diff(): - assert_env() - dst_root = upath() + assert_setup() + dst_root = cpath() src_root = dpath( "scratchpad" ,"stage" @@ -250,12 +250,12 @@ def cmd_diff(): print("No differences found. Release matches stage.") def cmd_clean(): - assert_env() - user_root_dir = upath() - if( not os.path.isdir(user_root_dir) ): + assert_setup() + consumer_root_dir = cpath() + if( not os.path.isdir(consumer_root_dir) ): return - for name in os.listdir(user_root_dir): - p = os.path.join(user_root_dir ,name) + for name in os.listdir(consumer_root_dir): + p = os.path.join(consumer_root_dir ,name) if( os.path.isdir(p) and not os.path.islink(p) ): shutil.rmtree(p ,ignore_errors=True) else: @@ -272,7 +272,7 @@ def CLI(): elif(cmd == "clean"): cmd_clean() elif(cmd == "ls"): - list_tree(upath()) + list_tree(cpath()) elif(cmd == "diff"): cmd_diff() elif(cmd == "help"):