An experimental TypeSpec syntax for Lexicon

wah

+127 -97
+2
packages/website/package.json
··· 11 11 "dependencies": { 12 12 "@astrojs/react": "^4.4.0", 13 13 "@tylex/emitter": "workspace:*", 14 + "@typespec/bundler": "^0.4.4", 15 + "@typespec/playground": "^0.11.0", 14 16 "astro": "^5.14.1", 15 17 "json-stringify-pretty-compact": "^4.0.0", 16 18 "react": "^19.2.0",
+32 -74
packages/website/src/components/Playground.tsx
··· 1 - import { useState } from 'react'; 2 - 3 1 export default function Playground() { 4 - const [tylexCode, setTylexCode] = useState(`@lexicon("1") 5 - @nsid("app.bsky.feed.post") 6 - namespace AppBskyFeedPost; 7 - 8 - @record 9 - @key("tid") 10 - model Main { 11 - @required text: string; 12 - @required createdAt: datetime; 13 - }`); 14 - 15 - const [lexiconOutput] = useState('// Playground coming soon...'); 16 - 17 2 return ( 18 3 <div style={{ 19 4 background: 'white', 20 5 borderRadius: '12px', 21 6 boxShadow: '0 4px 6px rgba(0, 0, 0, 0.05)', 22 7 overflow: 'hidden', 8 + minHeight: '500px', 9 + display: 'flex', 10 + alignItems: 'center', 11 + justifyContent: 'center', 12 + padding: '2rem', 13 + color: '#64748b', 14 + fontFamily: 'system-ui, sans-serif', 23 15 }}> 24 - <div style={{ 25 - display: 'grid', 26 - gridTemplateColumns: '1fr 1fr', 27 - borderBottom: '2px solid #f8fafc', 28 - background: '#fafafa', 29 - }}> 30 - <h3 style={{ 31 - padding: '1rem', 32 - textAlign: 'center', 33 - fontSize: '1rem', 34 - color: '#64748b', 35 - fontWeight: 600, 36 - margin: 0, 37 - }}> 38 - Tylex Input 16 + <div style={{ textAlign: 'center', maxWidth: '600px' }}> 17 + <h3 style={{ fontSize: '1.5rem', marginBottom: '1rem', color: '#1e293b', fontWeight: 700 }}> 18 + Try it in the Playground 39 19 </h3> 40 - <h3 style={{ 41 - padding: '1rem', 42 - textAlign: 'center', 43 - fontSize: '1rem', 44 - color: '#64748b', 45 - fontWeight: 600, 46 - margin: 0, 47 - }}> 48 - Lexicon Output 49 - </h3> 50 - </div> 51 - 52 - <div style={{ 53 - display: 'grid', 54 - gridTemplateColumns: '1fr 1fr', 55 - minHeight: '400px', 56 - }}> 57 - <div style={{ borderRight: '1px solid #f8fafc' }}> 58 - <textarea 59 - value={tylexCode} 60 - onChange={(e) => setTylexCode(e.target.value)} 61 - style={{ 62 - width: '100%', 63 - height: '100%', 64 - padding: '1.5rem', 65 - border: 'none', 66 - fontFamily: 'Monaco, Menlo, monospace', 67 - fontSize: '0.875rem', 68 - resize: 'none', 69 - outline: 'none', 70 - }} 71 - placeholder="Enter Tylex code here..." 72 - /> 73 - </div> 74 - 75 - <div style={{ 76 - padding: '1.5rem', 77 - fontFamily: 'Monaco, Menlo, monospace', 78 - fontSize: '0.875rem', 79 - color: '#64748b', 80 - overflowX: 'auto', 81 - }}> 82 - <pre style={{ margin: 0 }}>{lexiconOutput}</pre> 83 - </div> 20 + <p style={{ fontSize: '1.125rem', marginBottom: '2rem', lineHeight: '1.6' }}> 21 + Experience tylex with syntax highlighting, live compilation, and instant Lexicon output. 22 + </p> 23 + <a 24 + href="http://localhost:5174" 25 + target="_blank" 26 + rel="noopener noreferrer" 27 + style={{ 28 + display: 'inline-block', 29 + padding: '0.875rem 2rem', 30 + background: 'linear-gradient(135deg, #7a8ef7 0%, #9483f7 70%, #b87ed8 100%)', 31 + color: 'white', 32 + textDecoration: 'none', 33 + borderRadius: '8px', 34 + fontWeight: 700, 35 + fontSize: '1.125rem', 36 + boxShadow: '0 2px 4px rgba(122, 142, 247, 0.35), 0 3px 6px rgba(0, 0, 0, 0.1)', 37 + transition: 'all 0.2s', 38 + }} 39 + > 40 + Open Playground → 41 + </a> 84 42 </div> 85 43 </div> 86 44 );
+93 -23
packages/website/src/pages/index.astro
··· 130 130 <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> 131 131 <meta name="viewport" content="width=device-width" /> 132 132 <meta name="generator" content={Astro.generator} /> 133 - <title>tylex – TypeSpec for AT Protocol Lexicons</title> 133 + <title>tylex – TypeSpec for AT protocol Lexicons</title> 134 134 </head> 135 135 <body> 136 136 <div class="container"> ··· 198 198 </p> 199 199 200 200 <div class="hero-actions"> 201 - <a href="#install" class="install-cta">Install</a> 201 + <a href="#install" class="install-cta">Try It</a> 202 202 <a href="https://tangled.org/@danabra.mov/typlex" target="_blank" rel="noopener noreferrer" class="star-btn"> 203 203 Read Documentation 204 204 </a> ··· 231 231 232 232 <div class="separator"></div> 233 233 234 - <section class="playground-section"> 235 - <h2>Playground</h2> 236 - <Playground client:load /> 237 - </section> 238 - 239 - <div class="separator"></div> 240 - 241 234 <section class="install-section" id="install"> 242 235 <h2>Install</h2> 243 236 <div class="install-grid"> 244 237 <div class="install-notice"> 245 - <p class="notice-text">This is an early alpha software. If you ship with borked lexicons, it’s not my fault.</p> 238 + <p class="notice-text">This is an early-stage experiment. There are bugs. You must verify the output!</p> 246 239 </div> 240 + <div class="install-step playground-step"> 241 + <div class="step-number">0</div> 242 + <div class="step-content"> 243 + <h3>Try the playground</h3> 244 + <p class="step-description">Experiment with typelex in your browser before installing.</p> 245 + <a href="http://localhost:5174" target="_blank" rel="noopener noreferrer" class="playground-button"> 246 + Open Playground 247 + </a> 248 + </div> 249 + </div> 250 + 247 251 <div class="install-step"> 248 252 <div class="step-number">1</div> 249 253 <div class="step-content"> ··· 283 287 <div class="step-content"> 284 288 <h3>Set up VS Code</h3> 285 289 <p class="step-description">Install the <a href="https://typespec.io/docs/introduction/editor/vscode/" target="_blank" rel="noopener noreferrer">TypeSpec for VS Code extension</a> for syntax highlighting and IntelliSense.</p> 290 + </div> 291 + </div> 292 + 293 + <div class="install-step"> 294 + <div class="step-number">5</div> 295 + <div class="step-content"> 296 + <h3>Read the docs</h3> 297 + <p class="step-description">Check out the <a href="https://tangled.org/@danabra.mov/typlex" target="_blank" rel="noopener noreferrer">documentation</a> to learn more about Typelex syntax and features.</p> 286 298 </div> 287 299 </div> 288 300 </div> ··· 718 730 719 731 .install-step { 720 732 position: relative; 733 + overflow: hidden; 734 + } 735 + 736 + @media (min-width: 768px) { 737 + .install-step { 738 + overflow: visible; 739 + } 721 740 } 722 741 723 742 .step-number { 724 - position: absolute; 725 - left: -3.5rem; 726 - top: 0.125rem; 743 + display: inline-flex; 744 + align-items: center; 745 + justify-content: center; 727 746 width: 2.25rem; 728 747 height: 2.25rem; 729 748 background: linear-gradient(135deg, #7a8ef7, #ff85c1); 730 749 color: white; 731 750 border-radius: 50%; 732 - display: flex; 733 - align-items: center; 734 - justify-content: center; 735 751 font-weight: 700; 736 752 font-size: 0.9375rem; 753 + margin-bottom: 1rem; 737 754 } 738 755 739 756 .step-content { 740 757 width: 100%; 741 758 } 742 759 760 + @media (min-width: 768px) { 761 + .step-number { 762 + position: absolute; 763 + left: -4rem; 764 + top: 0.125rem; 765 + width: 2.5rem; 766 + height: 2.5rem; 767 + font-size: 1rem; 768 + margin-bottom: 0; 769 + } 770 + } 771 + 743 772 .step-content h3 { 744 773 font-size: 1.25rem; 745 774 margin: 0 0 1.25rem 0; ··· 822 851 823 852 .install-grid { 824 853 gap: 3.5rem; 825 - } 826 - 827 - .step-number { 828 - left: -4rem; 829 - width: 2.5rem; 830 - height: 2.5rem; 831 - font-size: 1rem; 832 854 } 833 855 834 856 .step-content h3 { ··· 1041 1063 1042 1064 .playground-section { 1043 1065 margin: 0; 1066 + } 1067 + 1068 + .playground-button { 1069 + display: inline-block; 1070 + margin-top: 1.25rem; 1071 + padding: 0.875rem 2rem; 1072 + background: linear-gradient(135deg, #7a8ef7 0%, #9483f7 70%, #b87ed8 100%); 1073 + color: white; 1074 + text-decoration: none; 1075 + border-radius: 8px; 1076 + font-weight: 700; 1077 + font-size: 1.0625rem; 1078 + letter-spacing: 0.01em; 1079 + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); 1080 + box-shadow: 1081 + 0 1px 0 0 rgba(255, 255, 255, 0.25) inset, 1082 + 0 -1px 0 0 rgba(0, 0, 0, 0.1) inset, 1083 + 0 0 0 1px rgba(122, 142, 247, 0.3), 1084 + 0 2px 4px rgba(122, 142, 247, 0.35), 1085 + 0 3px 6px rgba(0, 0, 0, 0.1); 1086 + border: none; 1087 + } 1088 + 1089 + .playground-button:hover { 1090 + background: linear-gradient(135deg, #7384e7 0%, #8d7ce7 70%, #b175d1 100%); 1091 + box-shadow: 1092 + 0 1px 0 0 rgba(255, 255, 255, 0.3) inset, 1093 + 0 -1px 0 0 rgba(0, 0, 0, 0.12) inset, 1094 + 0 0 0 1px rgba(122, 142, 247, 0.4), 1095 + 0 3px 6px rgba(122, 142, 247, 0.4), 1096 + 0 4px 8px rgba(0, 0, 0, 0.12); 1097 + transform: translateY(-1px); 1098 + } 1099 + 1100 + .playground-button:active { 1101 + background: linear-gradient(135deg, #6a7ee5 0%, #8473e5 70%, #a86ec8 100%); 1102 + box-shadow: 1103 + 0 0 0 1px rgba(122, 142, 247, 0.5), 1104 + 0 1px 2px rgba(0, 0, 0, 0.2) inset, 1105 + 0 2px 4px rgba(0, 0, 0, 0.15); 1106 + transform: translateY(1px); 1107 + } 1108 + 1109 + @media (min-width: 768px) { 1110 + .playground-button { 1111 + font-size: 1.125rem; 1112 + padding: 1rem 2.5rem; 1113 + } 1044 1114 } 1045 1115 1046 1116 footer {