@miliastry/quasar 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/LICENSE +119 -0
  2. package/README.md +45 -0
  3. package/dist/Visuals/lyne.css +1027 -0
  4. package/dist/Visuals/osu.css +257 -0
  5. package/dist/index.d.mts +4563 -0
  6. package/dist/index.d.ts +4563 -0
  7. package/dist/index.js +11291 -0
  8. package/dist/index.mjs +11197 -0
  9. package/package.json +51 -0
  10. package/src/Analysis/Contracts/AnalysisReport.ts +26 -0
  11. package/src/Analysis/Contracts/Contribution.ts +76 -0
  12. package/src/Analysis/Contracts/Pass.ts +76 -0
  13. package/src/Analysis/Contracts/PipelineContext.ts +48 -0
  14. package/src/Analysis/Passes/Analysis/GradientAnalyzer.ts +292 -0
  15. package/src/Analysis/Passes/Analysis/MergeableColorAnalyzer.ts +112 -0
  16. package/src/Analysis/Passes/Analysis/RainbowAnalyzer.ts +233 -0
  17. package/src/Analysis/Passes/Analysis/WaveAnalyzer.ts +211 -0
  18. package/src/Analysis/Passes/Analysis/__tests__/GradientAnalyzer.test.ts +135 -0
  19. package/src/Analysis/Passes/Analysis/__tests__/MergeableColorAnalyzer.test.ts +84 -0
  20. package/src/Analysis/Passes/Analysis/__tests__/RainbowAnalyzer.test.ts +99 -0
  21. package/src/Analysis/Passes/Analysis/__tests__/WaveAnalyzer.test.ts +119 -0
  22. package/src/Analysis/Passes/Decision/DefaultDecision.ts +139 -0
  23. package/src/Analysis/Passes/Decision/__tests__/DefaultDecision.test.ts +179 -0
  24. package/src/Analysis/Passes/Transform/CollapseGradientTransform.ts +176 -0
  25. package/src/Analysis/Passes/Transform/MergeColorsTransform.ts +126 -0
  26. package/src/Analysis/Passes/Transform/RainbowCollapseTransform.ts +83 -0
  27. package/src/Analysis/Passes/Transform/WaveCollapseTransform.ts +88 -0
  28. package/src/Analysis/Passes/Utility/CharacterCountAnalyzer.ts +45 -0
  29. package/src/Analysis/Pipeline/Pipeline.ts +133 -0
  30. package/src/Analysis/Pipeline/PipelineBuilder.ts +55 -0
  31. package/src/Analysis/Pipeline/PipelineStage.ts +19 -0
  32. package/src/Analysis/Utils/color-utils.ts +132 -0
  33. package/src/Analysis/__tests__/Integration.test.ts +162 -0
  34. package/src/Analysis/__tests__/Pipeline.test.ts +133 -0
  35. package/src/Analysis/index.ts +52 -0
  36. package/src/BBCode/BBCodeDocumentModel.ts +175 -0
  37. package/src/BBCode/BBCodeToGreenNode.ts +755 -0
  38. package/src/BBCode/Parser.ts +384 -0
  39. package/src/BBCode/index.ts +12 -0
  40. package/src/Collab/positions.ts +91 -0
  41. package/src/Commands/Command.ts +44 -0
  42. package/src/Commands/CommandRegistry.ts +78 -0
  43. package/src/Commands/DeleteNode.ts +20 -0
  44. package/src/Commands/InsertText.ts +21 -0
  45. package/src/Commands/SplitMerge.ts +28 -0
  46. package/src/Commands/WrapInTag.ts +21 -0
  47. package/src/Commands/index.ts +6 -0
  48. package/src/Diff/TreeDiffer.ts +264 -0
  49. package/src/Diff/__tests__/TreeDiffer.test.ts +65 -0
  50. package/src/Diff/index.ts +2 -0
  51. package/src/Events/EventBus.ts +160 -0
  52. package/src/Events/index.ts +2 -0
  53. package/src/Formatter/Formatter.ts +54 -0
  54. package/src/Formatter/index.ts +2 -0
  55. package/src/HTML/HTMLDocumentModel.ts +35 -0
  56. package/src/HTML/HTMLToGreenNode.ts +290 -0
  57. package/src/Incremental/ChangeTracker.ts +105 -0
  58. package/src/Incremental/IncrementalParser.ts +591 -0
  59. package/src/Incremental/__tests__/IncrementalParser.test.ts +164 -0
  60. package/src/Incremental/index.ts +4 -0
  61. package/src/Lexer/BBCodeLexer.ts +382 -0
  62. package/src/Lexer/Lexer.ts +181 -0
  63. package/src/Lexer/index.ts +10 -0
  64. package/src/Linter/Linter.ts +193 -0
  65. package/src/Linter/index.ts +2 -0
  66. package/src/Markdown/MarkdownAST.ts +112 -0
  67. package/src/Markdown/MarkdownDocumentModel.ts +55 -0
  68. package/src/Markdown/MarkdownLexer.ts +203 -0
  69. package/src/Markdown/MarkdownParser.ts +455 -0
  70. package/src/Markdown/MarkdownToGreenNode.ts +153 -0
  71. package/src/Model/DocumentModel.ts +694 -0
  72. package/src/Model/NodeFactory.ts +117 -0
  73. package/src/Model/TagRegistry.ts +495 -0
  74. package/src/Model/index.ts +5 -0
  75. package/src/Plugins/PluginAPI.ts +119 -0
  76. package/src/Plugins/PluginRegistry.ts +132 -0
  77. package/src/Plugins/index.ts +3 -0
  78. package/src/Queries/QueryEngine.ts +152 -0
  79. package/src/Queries/index.ts +1 -0
  80. package/src/RenderPipeline/RenderPipeline.ts +125 -0
  81. package/src/RenderPipeline/RenderTree.ts +134 -0
  82. package/src/RenderPipeline/index.ts +4 -0
  83. package/src/Semantic/SemanticAnalyzer.ts +506 -0
  84. package/src/Semantic/index.ts +2 -0
  85. package/src/Symbols/SymbolTable.ts +124 -0
  86. package/src/Symbols/index.ts +1 -0
  87. package/src/Syntax/GreenNode.ts +324 -0
  88. package/src/Syntax/GreenNodePool.ts +269 -0
  89. package/src/Syntax/NodeMatcher.ts +370 -0
  90. package/src/Syntax/RedNode.ts +569 -0
  91. package/src/Syntax/RedNodeStore.ts +184 -0
  92. package/src/Syntax/TreeBuilder.ts +214 -0
  93. package/src/Syntax/__tests__/GreenNode.test.ts +33 -0
  94. package/src/Syntax/__tests__/RedNode.test.ts +81 -0
  95. package/src/Syntax/__tests__/RedNodeStore.test.ts +104 -0
  96. package/src/Syntax/greenEdit.ts +110 -0
  97. package/src/Syntax/hash.ts +30 -0
  98. package/src/Syntax/index.ts +12 -0
  99. package/src/Syntax/partition.ts +161 -0
  100. package/src/Syntax/preserveNodeIds.ts +201 -0
  101. package/src/Tests/ASTOptimizerIdempotence.test.ts +77 -0
  102. package/src/Tests/BlockPatcher.test.ts +437 -0
  103. package/src/Tests/BlockPatcherWindowed.test.ts +364 -0
  104. package/src/Tests/BoxDrawer.test.ts +217 -0
  105. package/src/Tests/BoxRichTitle.test.ts +105 -0
  106. package/src/Tests/Chars500kBenchmark.test.ts +151 -0
  107. package/src/Tests/Chars500kEdits.test.ts +321 -0
  108. package/src/Tests/CollabPositions.test.ts +146 -0
  109. package/src/Tests/CompilerPathProfiling.test.ts +186 -0
  110. package/src/Tests/DOMMorpher.test.ts +142 -0
  111. package/src/Tests/DomPatchPerf.test.ts +60 -0
  112. package/src/Tests/EffectSegments.snapshot.json +616 -0
  113. package/src/Tests/EffectSegments.test.ts +68 -0
  114. package/src/Tests/FindNodeAtOffset.test.ts +65 -0
  115. package/src/Tests/Fuzzer.test.ts +166 -0
  116. package/src/Tests/GreenNodePool.test.ts +153 -0
  117. package/src/Tests/Lexer.test.ts +238 -0
  118. package/src/Tests/LyneMode.test.ts +187 -0
  119. package/src/Tests/ModelCoherence.test.ts +180 -0
  120. package/src/Tests/Partition.test.ts +238 -0
  121. package/src/Tests/PluginTags.test.ts +150 -0
  122. package/src/Tests/ProblematicSection.test.ts +46 -0
  123. package/src/Tests/ProblematicSectionHTML.test.ts +58 -0
  124. package/src/Tests/RedReuse.test.ts +134 -0
  125. package/src/Tests/ReproDelete20k.test.ts +62 -0
  126. package/src/Tests/SemanticValidators.test.ts +136 -0
  127. package/src/Tests/StableNodeIds.test.ts +210 -0
  128. package/src/Tests/StudioColorBloat.test.ts +25 -0
  129. package/src/Tests/StudioDebugText.test.ts +27 -0
  130. package/src/Tests/StudioTrailingChar.test.ts +25 -0
  131. package/src/Tests/StudioValidText.test.ts +25 -0
  132. package/src/Tests/UrlImgBug.test.ts +23 -0
  133. package/src/Tests/VisualBuilderFidelity.test.ts +105 -0
  134. package/src/Tests/referenceDocument.ts +119 -0
  135. package/src/Transactions/Transaction.ts +176 -0
  136. package/src/Transactions/UndoManager.ts +111 -0
  137. package/src/Transactions/index.ts +3 -0
  138. package/src/Transformers/ASTOptimizer.ts +315 -0
  139. package/src/Transformers/GradientTransformer.ts +143 -0
  140. package/src/Transformers/GrowTransformer.ts +115 -0
  141. package/src/Transformers/RainbowTransformer.ts +121 -0
  142. package/src/Transformers/SineWaveTransformer.ts +130 -0
  143. package/src/Transformers/Transformer.ts +22 -0
  144. package/src/Types/core.ts +270 -0
  145. package/src/Types/diagnostics.ts +156 -0
  146. package/src/Types/index.ts +23 -0
  147. package/src/Types/operations.ts +180 -0
  148. package/src/Types/queries.ts +121 -0
  149. package/src/Types/symbols.ts +69 -0
  150. package/src/Types/tokens.ts +186 -0
  151. package/src/Utils/BBCodeGenerator.ts +126 -0
  152. package/src/Utils/ColorMath.ts +276 -0
  153. package/src/Utils/color.ts +112 -0
  154. package/src/Utils/dom-to-svg.test.ts +86 -0
  155. package/src/Utils/dom-to-svg.ts +615 -0
  156. package/src/Utils/treeTransformers.ts +717 -0
  157. package/src/Visitors/BBBlocksExporter.ts +69 -0
  158. package/src/Visitors/BBCodeExporter.ts +318 -0
  159. package/src/Visitors/BlockPatcher.ts +963 -0
  160. package/src/Visitors/DOMMorpher.ts +134 -0
  161. package/src/Visitors/HTMLRenderer.ts +1077 -0
  162. package/src/Visitors/JSONExporter.ts +66 -0
  163. package/src/Visitors/MarkdownExporter.ts +99 -0
  164. package/src/Visitors/SVGRenderer.ts +35 -0
  165. package/src/Visitors/TiptapExporter.ts +145 -0
  166. package/src/Visitors/Visitor.ts +48 -0
  167. package/src/Visitors/index.ts +9 -0
  168. package/src/Visuals/BoxDrawer.ts +175 -0
  169. package/src/Visuals/index.ts +42 -0
  170. package/src/Visuals/lyne.css +1027 -0
  171. package/src/Visuals/osu.css +257 -0
  172. package/src/index.ts +197 -0
@@ -0,0 +1,121 @@
1
+ /**
2
+ * DocumentEngine — Query Types
3
+ *
4
+ * CSS-inspired query selectors for the document tree.
5
+ * Enables queries like: `paragraph > bold`, `quote text`, `[color]`
6
+ */
7
+
8
+ import type { NodeKind, NodeAttributes } from './core'
9
+ import type { RedNode } from '../Syntax/RedNode'
10
+
11
+ // ─── Query ─────────────────────────────────────────────────────
12
+
13
+ export type QuerySelector =
14
+ | { type: 'kind'; kind: NodeKind }
15
+ | { type: 'tag'; tag: string }
16
+ | { type: 'attribute'; name: string; value?: string }
17
+ | { type: 'has_attribute'; name: string }
18
+ | { type: 'text'; pattern: string }
19
+ | { type: 'has_child'; selector: QuerySelector }
20
+ | { type: 'has_descendant'; selector: QuerySelector }
21
+ | { type: 'nth_child'; n: number }
22
+ | { type: 'first_child' }
23
+ | { type: 'last_child' }
24
+ | { type: 'position'; min?: number; max?: number }
25
+ | { type: 'depth'; min?: number; max?: number }
26
+
27
+ export type QueryCombinator =
28
+ | 'child' // > direct child
29
+ | 'descendant' // (space) any descendant
30
+ | 'adjacent' // + next sibling
31
+ | 'sibling' // ~ any sibling
32
+
33
+ export interface QueryStep {
34
+ selector: QuerySelector[]
35
+ combinator: QueryCombinator | 'root'
36
+ }
37
+
38
+ export interface Query {
39
+ steps: QueryStep[]
40
+ }
41
+
42
+ export interface QueryMatch {
43
+ node: RedNode
44
+ /** How well this node matches (for sorting results) */
45
+ score: number
46
+ /** The specific selectors that matched */
47
+ matchedSelectors: string[]
48
+ }
49
+
50
+ // ─── Query Builder ─────────────────────────────────────────────
51
+
52
+ export function query(selector: string): Query {
53
+ // Parse simple CSS-like selector syntax
54
+ const parts = selector.split(/\s+(?![^\[]*\])\s*/g).filter(Boolean)
55
+ const steps: QueryStep[] = []
56
+ let combinator: QueryCombinator | 'root' = 'root'
57
+
58
+ for (const part of parts) {
59
+ if (part === '>') {
60
+ combinator = 'child'
61
+ continue
62
+ }
63
+ if (part === '+') {
64
+ combinator = 'adjacent'
65
+ continue
66
+ }
67
+ if (part === '~') {
68
+ combinator = 'sibling'
69
+ continue
70
+ }
71
+
72
+ const selectors: QuerySelector[] = parseSimpleSelector(part)
73
+ steps.push({ selector: selectors, combinator })
74
+ combinator = 'descendant'
75
+ }
76
+
77
+ return { steps }
78
+ }
79
+
80
+ function parseSimpleSelector(part: string): QuerySelector[] {
81
+ const selectors: QuerySelector[] = []
82
+
83
+ // Tag/kind selector
84
+ const tagMatch = part.match(/^([a-zA-Z_*][a-zA-Z0-9_-]*)/)
85
+ if (tagMatch) {
86
+ selectors.push({ type: 'tag', tag: tagMatch[1] })
87
+ }
88
+
89
+ // Attribute selectors: [name], [name=value]
90
+ const attrRegex = /\[([^\]=]+)(?:=([^\]]*))?\]/g
91
+ let attrMatch
92
+ while ((attrMatch = attrRegex.exec(part)) !== null) {
93
+ if (attrMatch[2] !== undefined) {
94
+ selectors.push({ type: 'attribute', name: attrMatch[1], value: attrMatch[2] })
95
+ } else {
96
+ selectors.push({ type: 'has_attribute', name: attrMatch[1] })
97
+ }
98
+ }
99
+
100
+ // Pseudo-classes
101
+ if (part.includes(':first-child')) {
102
+ selectors.push({ type: 'first_child' })
103
+ }
104
+ if (part.includes(':last-child')) {
105
+ selectors.push({ type: 'last_child' })
106
+ }
107
+ const nthMatch = part.match(/:nth-child\((\d+)\)/)
108
+ if (nthMatch) {
109
+ selectors.push({ type: 'nth_child', n: parseInt(nthMatch[1], 10) })
110
+ }
111
+
112
+ return selectors
113
+ }
114
+
115
+ // ─── Query Result ─────────────────────────────────────────────
116
+
117
+ export interface QueryResult {
118
+ matches: QueryMatch[]
119
+ total: number
120
+ time: number
121
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * DocumentEngine — Symbol Types
3
+ *
4
+ * Symbol system for definitions and references in BBCode documents.
5
+ * Enables: go-to-definition, find-all-references, rename, etc.
6
+ *
7
+ * Inspired by Language Server Protocol (LSP).
8
+ */
9
+
10
+ import type { NodeId, NodeKind, SourceRange } from './core'
11
+
12
+ // ─── Symbol Kind ───────────────────────────────────────────────
13
+
14
+ export type SymbolKind =
15
+ | 'tag'
16
+ | 'id_definition'
17
+ | 'id_reference'
18
+ | 'class'
19
+ | 'property'
20
+ | 'variable'
21
+ | 'function'
22
+ | 'module'
23
+ | 'snippet'
24
+ | 'template'
25
+ | 'other'
26
+
27
+ // ─── Symbol Info ───────────────────────────────────────────────
28
+
29
+ export interface SymbolInfo {
30
+ /** Unique ID for this symbol */
31
+ id: string
32
+ /** Symbol name (e.g. 'hero', 'my-block') */
33
+ name: string
34
+ /** Symbol kind */
35
+ kind: SymbolKind
36
+ /** The node that defines this symbol */
37
+ definitionNodeId: NodeId
38
+ /** Source range of the definition */
39
+ definitionRange: SourceRange
40
+ /** Container node (e.g. the document) */
41
+ containerNodeId: NodeId
42
+ /** All references to this symbol */
43
+ references: Reference[]
44
+ /** Additional data */
45
+ data?: Record<string, unknown>
46
+ }
47
+
48
+ // ─── Reference ─────────────────────────────────────────────────
49
+
50
+ export interface Reference {
51
+ nodeId: NodeId
52
+ range: SourceRange
53
+ kind: 'definition' | 'reference' | 'implementation'
54
+ }
55
+
56
+ // ─── Symbol Table ──────────────────────────────────────────────
57
+
58
+ export interface SymbolTableData {
59
+ symbols: Map<string, SymbolInfo>
60
+ /** Quick lookup: node ID → symbol */
61
+ nodeToSymbol: Map<NodeId, string>
62
+ }
63
+
64
+ // ─── Symbol Search ─────────────────────────────────────────────
65
+
66
+ export interface SymbolSearchResult {
67
+ symbol: SymbolInfo
68
+ score: number
69
+ }
@@ -0,0 +1,186 @@
1
+ /**
2
+ * DocumentEngine — Token & Trivia Types
3
+ *
4
+ * Tokens are the output of the Lexer and input to the Parser.
5
+ * Trivia is whitespace, newlines, formatting that is preserved
6
+ * but not semantically meaningful — inspired by Roslyn.
7
+ */
8
+
9
+ // ─── Trivia ────────────────────────────────────────────────────
10
+
11
+ export type TriviaKind =
12
+ | 'whitespace'
13
+ | 'newline'
14
+ | 'carriage_return'
15
+ | 'comment'
16
+ | 'line_continuation'
17
+
18
+ export interface Trivia {
19
+ kind: TriviaKind
20
+ text: string
21
+ range: Range
22
+ }
23
+
24
+ // ─── Range ─────────────────────────────────────────────────────
25
+
26
+ export interface Range {
27
+ start: number
28
+ end: number
29
+ }
30
+
31
+ export function range(start: number, end: number): Range {
32
+ return { start, end }
33
+ }
34
+
35
+ export function rangeLength(r: Range): number {
36
+ return r.end - r.start
37
+ }
38
+
39
+ export function rangeContains(r: Range, pos: number): boolean {
40
+ return pos >= r.start && pos < r.end
41
+ }
42
+
43
+ // ─── Token ─────────────────────────────────────────────────────
44
+
45
+ export type TokenKind =
46
+ // Structural
47
+ | 'open_tag'
48
+ | 'close_tag'
49
+ | 'self_closing_tag'
50
+ | 'text'
51
+ | 'newline'
52
+ | 'whitespace'
53
+ // Attribute values (inside tags)
54
+ | 'attribute_name'
55
+ | 'attribute_value'
56
+ | 'equals'
57
+ | 'quote'
58
+ // Special
59
+ | 'end_of_file'
60
+ | 'error'
61
+
62
+ export interface Token {
63
+ kind: TokenKind
64
+ text: string
65
+ range: Range
66
+
67
+ /** Tag name or attribute name if applicable */
68
+ name?: string
69
+
70
+ /** Full raw tag text including brackets: [b], [/color], [*] */
71
+ tagText?: string
72
+
73
+ /** Raw attribute string (everything between tag name and closing ]) */
74
+ attrs?: string
75
+
76
+ /** Trivia attached BEFORE this token (whitespace, newlines) */
77
+ leadingTrivia: Trivia[]
78
+
79
+ /** Trivia attached AFTER this token */
80
+ trailingTrivia: Trivia[]
81
+ }
82
+
83
+ // ─── Token Stream ──────────────────────────────────────────────
84
+
85
+ export interface TokenStream {
86
+ /** Peek at the current token without consuming */
87
+ peek(): Token
88
+ /** Consume and return the current token, advance */
89
+ advance(): Token
90
+ /** Look ahead n tokens (0 = next) */
91
+ lookAhead(n: number): Token
92
+ /** Whether we've consumed all tokens */
93
+ isEof(): boolean
94
+ /** Get current position in the stream */
95
+ position(): number
96
+ /** Reset to a position */
97
+ seek(pos: number): void
98
+ /** Get all remaining tokens */
99
+ remaining(): Token[]
100
+ /** Get all tokens */
101
+ getAll(): Token[]
102
+ }
103
+
104
+ // ─── Token Factory ─────────────────────────────────────────────
105
+
106
+ export function createToken(
107
+ kind: TokenKind,
108
+ text: string,
109
+ start: number,
110
+ end: number,
111
+ options?: {
112
+ name?: string
113
+ tagText?: string
114
+ attrs?: string
115
+ leadingTrivia?: Trivia[]
116
+ trailingTrivia?: Trivia[]
117
+ },
118
+ ): Token {
119
+ return {
120
+ kind,
121
+ text,
122
+ range: range(start, end),
123
+ name: options?.name,
124
+ tagText: options?.tagText,
125
+ attrs: options?.attrs,
126
+ leadingTrivia: options?.leadingTrivia ?? [],
127
+ trailingTrivia: options?.trailingTrivia ?? [],
128
+ }
129
+ }
130
+
131
+ export function createTrivia(kind: TriviaKind, text: string, start: number, end: number): Trivia {
132
+ return { kind, text, range: range(start, end) }
133
+ }
134
+
135
+ // ─── Token Stream Implementation ───────────────────────────────
136
+
137
+ export class ArrayTokenStream implements TokenStream {
138
+ private tokens: Token[]
139
+ private pos: number
140
+
141
+ constructor(tokens: Token[]) {
142
+ this.tokens = tokens
143
+ this.pos = 0
144
+ }
145
+
146
+ peek(): Token {
147
+ if (this.pos >= this.tokens.length) {
148
+ return createToken('end_of_file', '', 0, 0)
149
+ }
150
+ return this.tokens[this.pos]
151
+ }
152
+
153
+ advance(): Token {
154
+ const token = this.peek()
155
+ this.pos++
156
+ return token
157
+ }
158
+
159
+ lookAhead(n: number): Token {
160
+ const idx = this.pos + n
161
+ if (idx >= this.tokens.length) {
162
+ return createToken('end_of_file', '', 0, 0)
163
+ }
164
+ return this.tokens[idx]
165
+ }
166
+
167
+ isEof(): boolean {
168
+ return this.pos >= this.tokens.length
169
+ }
170
+
171
+ position(): number {
172
+ return this.pos
173
+ }
174
+
175
+ seek(pos: number): void {
176
+ this.pos = Math.max(0, Math.min(pos, this.tokens.length))
177
+ }
178
+
179
+ remaining(): Token[] {
180
+ return this.tokens.slice(this.pos)
181
+ }
182
+
183
+ getAll(): Token[] {
184
+ return [...this.tokens]
185
+ }
186
+ }
@@ -0,0 +1,126 @@
1
+ export type GeneratorTheme = 'forum_post' | 'beatmap_desc' | 'random_chat'
2
+
3
+ export class BBCodeGenerator {
4
+ // ── Osu! Dictionaries ──
5
+ private nouns = ['beatmap', 'circles', 'sliders', 'streams', 'jumps', 'combo', 'PP', 'rank', 'choke', 'mods', 'FC', 'spinner', 'accuracy', 'cursor']
6
+ private adjectives = ['hard', 'insane', 'unranked', 'ranked', 'loved', 'fast', 'tricky', 'technical', 'smooth', 'impossible']
7
+ private verbs = ['click', 'hit', 'miss', 'retry', 'pass', 'fail', 'spin', 'farm']
8
+ private players = ['peppy', 'Cookiezi', 'mrekk', 'Vaxei', 'WhiteCat', 'BTMC']
9
+ private colors = ['red', 'blue', 'green', 'purple', '#FF5555', '#55FF55']
10
+
11
+ private randomInt(max: number): number {
12
+ return Math.floor(Math.random() * max)
13
+ }
14
+
15
+ private pick<T>(arr: T[]): T {
16
+ return arr[this.randomInt(arr.length)]
17
+ }
18
+
19
+ // ── Basic Text Generators ──
20
+
21
+ private generateSentence(): string {
22
+ const templates = [
23
+ `I just got a ${this.pick(this.adjectives)} ${this.pick(this.nouns)} on this map!`,
24
+ `Why are the ${this.pick(this.nouns)} so ${this.pick(this.adjectives)} here?`,
25
+ `Don't forget to ${this.pick(this.verbs)} the ${this.pick(this.nouns)}...`,
26
+ `${this.pick(this.players)} could probably ${this.pick(this.verbs)} this with HardRock.`,
27
+ `My ${this.pick(this.nouns)} is completely ruined after that ${this.pick(this.nouns)}.`
28
+ ]
29
+ return this.pick(templates)
30
+ }
31
+
32
+ private generateParagraph(sentences: number = 3): string {
33
+ let p = ''
34
+ for (let i = 0; i < sentences; i++) {
35
+ p += this.generateSentence() + ' '
36
+ }
37
+ return p.trim()
38
+ }
39
+
40
+ // ── Formatted Blocks ──
41
+
42
+ private generateHeading(level: 1 | 2 | 3 = 1): string {
43
+ const text = `${this.pick(this.adjectives)} ${this.pick(this.nouns)}!`
44
+ const size = level === 1 ? 150 : level === 2 ? 120 : 100
45
+ return `[size=${size}][b]${text.toUpperCase()}[/b][/size]`
46
+ }
47
+
48
+ private generateHighlight(): string {
49
+ return `[color=${this.pick(this.colors)}][b]${this.generateSentence()}[/b][/color]`
50
+ }
51
+
52
+ private generateList(items: number = 3): string {
53
+ let list = '[list]\n'
54
+ for (let i = 0; i < items; i++) {
55
+ list += `[*] ${this.pick(this.adjectives)} ${this.pick(this.nouns)}\n`
56
+ }
57
+ list += '[/list]'
58
+ return list
59
+ }
60
+
61
+ private generateNotice(): string {
62
+ return `[notice]\n[b]Update:[/b] ${this.generateSentence()}\n[/notice]`
63
+ }
64
+
65
+ private generateBox(): string {
66
+ return `[box=${this.pick(this.nouns)} details]\n${this.generateParagraph(2)}\n[/box]`
67
+ }
68
+
69
+ // ── High-Level Document Templates ──
70
+
71
+ public generate(theme: GeneratorTheme = 'forum_post'): string {
72
+ switch (theme) {
73
+ case 'forum_post': return this.generateForumPost()
74
+ case 'beatmap_desc': return this.generateBeatmapDesc()
75
+ case 'random_chat': return this.generateRandomChat()
76
+ }
77
+ }
78
+
79
+ private generateForumPost(): string {
80
+ return `
81
+ ${this.generateHeading(1)}
82
+
83
+ Hey everyone!
84
+ ${this.generateParagraph(3)}
85
+
86
+ ${this.generateHighlight()}
87
+
88
+ Here is what I think about the ${this.pick(this.nouns)}:
89
+ ${this.generateList(4)}
90
+
91
+ ${this.generateBox()}
92
+
93
+ Thanks for reading!
94
+ [i]- ${this.pick(this.players)}[/i]
95
+ `.trim()
96
+ }
97
+
98
+ private generateBeatmapDesc(): string {
99
+ return `
100
+ [center]
101
+ ${this.generateHeading(1)}
102
+ [i]Mapped by ${this.pick(this.players)}[/i]
103
+ [/center]
104
+
105
+ ${this.generateNotice()}
106
+
107
+ [size=120][b]Difficulties[/b][/size]
108
+ ${this.generateList(3)}
109
+
110
+ [size=120][b]Modding[/b][/size]
111
+ ${this.generateParagraph(2)}
112
+ If you want to mod, please focus on the ${this.pick(this.adjectives)} ${this.pick(this.nouns)}.
113
+
114
+ [color=#888888]Enjoy the map![/color]
115
+ `.trim()
116
+ }
117
+
118
+ private generateRandomChat(): string {
119
+ return `
120
+ [b]PlayerA:[/b] omg I just had a ${this.pick(this.adjectives)} ${this.pick(this.nouns)}
121
+ [b]PlayerB:[/b] lol really? did you ${this.pick(this.verbs)} it?
122
+ [b]PlayerA:[/b] no... ${this.generateHighlight()}
123
+ [b]PlayerB:[/b] rip your ${this.pick(this.nouns)}.
124
+ `.trim()
125
+ }
126
+ }