@gramlot/gramlot-examples 0.2.2

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 (98) hide show
  1. package/LICENSE +176 -0
  2. package/NOTICE +8 -0
  3. package/README.md +49 -0
  4. package/package.json +52 -0
  5. package/src/gramlot_examples/catalog.json +253 -0
  6. package/src/gramlot_examples/gallery/dist/LICENSE +176 -0
  7. package/src/gramlot_examples/gallery/dist/NOTICE +8 -0
  8. package/src/gramlot_examples/gallery/dist/frame.js +28 -0
  9. package/src/gramlot_examples/gallery/dist/gallery.js +21239 -0
  10. package/src/gramlot_examples/gallery/dist/notices.json +38 -0
  11. package/src/gramlot_examples/gallery/gallery-page.js +97 -0
  12. package/src/gramlot_examples/gallery/gallery.css +110 -0
  13. package/src/gramlot_examples/gallery/page.js +26 -0
  14. package/src/gramlot_examples/index.js +79 -0
  15. package/src/gramlot_examples/pages/README.md +21 -0
  16. package/src/gramlot_examples/pages/binding/01_pointers.js +35 -0
  17. package/src/gramlot_examples/pages/binding/01_pointers.md +14 -0
  18. package/src/gramlot_examples/pages/binding/02_variable_datapath.js +33 -0
  19. package/src/gramlot_examples/pages/binding/02_variable_datapath.md +9 -0
  20. package/src/gramlot_examples/pages/binding/03_setters_and_defaults.js +42 -0
  21. package/src/gramlot_examples/pages/binding/03_setters_and_defaults.md +10 -0
  22. package/src/gramlot_examples/pages/binding/04_native_editing.js +53 -0
  23. package/src/gramlot_examples/pages/binding/04_native_editing.md +11 -0
  24. package/src/gramlot_examples/pages/binding/05_checkbox_radio.js +41 -0
  25. package/src/gramlot_examples/pages/binding/05_checkbox_radio.md +9 -0
  26. package/src/gramlot_examples/pages/binding/06_style_and_visibility.js +51 -0
  27. package/src/gramlot_examples/pages/binding/06_style_and_visibility.md +11 -0
  28. package/src/gramlot_examples/pages/binding/07_bound_svg.js +33 -0
  29. package/src/gramlot_examples/pages/binding/07_bound_svg.md +7 -0
  30. package/src/gramlot_examples/pages/binding/08_freeze.css +1 -0
  31. package/src/gramlot_examples/pages/binding/08_freeze.js +26 -0
  32. package/src/gramlot_examples/pages/binding/08_freeze.md +11 -0
  33. package/src/gramlot_examples/pages/binding/08_freeze_aux.js +26 -0
  34. package/src/gramlot_examples/pages/binding/09_forms.css +8 -0
  35. package/src/gramlot_examples/pages/binding/09_forms.js +79 -0
  36. package/src/gramlot_examples/pages/binding/09_forms.md +12 -0
  37. package/src/gramlot_examples/pages/binding/10_svg_shapes.js +42 -0
  38. package/src/gramlot_examples/pages/binding/10_svg_shapes.md +9 -0
  39. package/src/gramlot_examples/pages/binding/11_complete_page.css +5 -0
  40. package/src/gramlot_examples/pages/binding/11_complete_page.js +127 -0
  41. package/src/gramlot_examples/pages/binding/11_complete_page.md +12 -0
  42. package/src/gramlot_examples/pages/binding/README.md +9 -0
  43. package/src/gramlot_examples/pages/controllers/01_formula.js +34 -0
  44. package/src/gramlot_examples/pages/controllers/01_formula.md +12 -0
  45. package/src/gramlot_examples/pages/controllers/02_controller.js +44 -0
  46. package/src/gramlot_examples/pages/controllers/02_controller.md +10 -0
  47. package/src/gramlot_examples/pages/controllers/03_named_logic.js +28 -0
  48. package/src/gramlot_examples/pages/controllers/03_named_logic.md +12 -0
  49. package/src/gramlot_examples/pages/controllers/03_named_logic_aux.js +19 -0
  50. package/src/gramlot_examples/pages/controllers/04_inline_expressions.js +38 -0
  51. package/src/gramlot_examples/pages/controllers/04_inline_expressions.md +12 -0
  52. package/src/gramlot_examples/pages/controllers/05_node_methods.css +1 -0
  53. package/src/gramlot_examples/pages/controllers/05_node_methods.js +39 -0
  54. package/src/gramlot_examples/pages/controllers/05_node_methods.md +11 -0
  55. package/src/gramlot_examples/pages/controllers/06_button_controller.js +36 -0
  56. package/src/gramlot_examples/pages/controllers/06_button_controller.md +11 -0
  57. package/src/gramlot_examples/pages/controllers/07_events.js +38 -0
  58. package/src/gramlot_examples/pages/controllers/07_events.md +10 -0
  59. package/src/gramlot_examples/pages/controllers/08_remote_source.js +34 -0
  60. package/src/gramlot_examples/pages/controllers/08_remote_source.md +10 -0
  61. package/src/gramlot_examples/pages/controllers/08_remote_source_aux.js +8 -0
  62. package/src/gramlot_examples/pages/controllers/09_end_to_end.css +1 -0
  63. package/src/gramlot_examples/pages/controllers/09_end_to_end.js +67 -0
  64. package/src/gramlot_examples/pages/controllers/09_end_to_end.md +14 -0
  65. package/src/gramlot_examples/pages/controllers/09_end_to_end_aux.js +35 -0
  66. package/src/gramlot_examples/pages/controllers/README.md +11 -0
  67. package/src/gramlot_examples/pages/html_svg/01_hello_world.js +12 -0
  68. package/src/gramlot_examples/pages/html_svg/01_hello_world.md +9 -0
  69. package/src/gramlot_examples/pages/html_svg/02_text_and_links.js +28 -0
  70. package/src/gramlot_examples/pages/html_svg/02_text_and_links.md +9 -0
  71. package/src/gramlot_examples/pages/html_svg/03_lists.js +29 -0
  72. package/src/gramlot_examples/pages/html_svg/03_lists.md +9 -0
  73. package/src/gramlot_examples/pages/html_svg/04_semantic_page.js +25 -0
  74. package/src/gramlot_examples/pages/html_svg/04_semantic_page.md +9 -0
  75. package/src/gramlot_examples/pages/html_svg/05_tables.js +27 -0
  76. package/src/gramlot_examples/pages/html_svg/05_tables.md +9 -0
  77. package/src/gramlot_examples/pages/html_svg/06_forms.css +5 -0
  78. package/src/gramlot_examples/pages/html_svg/06_forms.js +63 -0
  79. package/src/gramlot_examples/pages/html_svg/06_forms.md +9 -0
  80. package/src/gramlot_examples/pages/html_svg/07_native_disclosure.js +27 -0
  81. package/src/gramlot_examples/pages/html_svg/07_native_disclosure.md +9 -0
  82. package/src/gramlot_examples/pages/html_svg/08_svg_shapes.js +23 -0
  83. package/src/gramlot_examples/pages/html_svg/08_svg_shapes.md +9 -0
  84. package/src/gramlot_examples/pages/html_svg/09_svg_composition.js +42 -0
  85. package/src/gramlot_examples/pages/html_svg/09_svg_composition.md +9 -0
  86. package/src/gramlot_examples/pages/html_svg/10_cards_with_icons.css +6 -0
  87. package/src/gramlot_examples/pages/html_svg/10_cards_with_icons.js +40 -0
  88. package/src/gramlot_examples/pages/html_svg/10_cards_with_icons.md +20 -0
  89. package/src/gramlot_examples/pages/html_svg/11_static_report.css +3 -0
  90. package/src/gramlot_examples/pages/html_svg/11_static_report.js +68 -0
  91. package/src/gramlot_examples/pages/html_svg/11_static_report.md +9 -0
  92. package/src/gramlot_examples/pages/html_svg/12_complete_page.css +5 -0
  93. package/src/gramlot_examples/pages/html_svg/12_complete_page.js +120 -0
  94. package/src/gramlot_examples/pages/html_svg/12_complete_page.md +9 -0
  95. package/src/gramlot_examples/pages/html_svg/13_live_source.css +3 -0
  96. package/src/gramlot_examples/pages/html_svg/13_live_source.js +64 -0
  97. package/src/gramlot_examples/pages/html_svg/13_live_source.md +18 -0
  98. package/src/gramlot_examples/pages/html_svg/README.md +10 -0
@@ -0,0 +1,68 @@
1
+ import {Page as BasePage} from '@gramlot/gramlot/page';
2
+
3
+ const REGIONS = [['North', 12, 'var(--gramlot-action)'], ['South', 9, 'var(--gramlot-warning)'], ['West', 15, 'var(--gramlot-success)']];
4
+
5
+ export class Page extends BasePage {
6
+ static title = 'Static report';
7
+ static css = ['/themes/gramlot-base/theme.css'];
8
+
9
+ main(root) {
10
+ const page = root.main({class: 'example-page stack'});
11
+ const header = page.header();
12
+ header.p('SAMPLE REPORT', {class: 'eyebrow'});
13
+ header.h1('Orders by region');
14
+ header.p('A fixed dataset feeds the summary, chart and table.');
15
+ const total = REGIONS.reduce((sum, region) => sum + region[1], 0);
16
+ this.summary(page, total);
17
+ this.chart(page);
18
+ this.table(page, total);
19
+ }
20
+
21
+ summary(page, total) {
22
+ const summary = page.section({class: 'grid report-summary', aria_label: 'Summary'});
23
+ this.stat(summary, 'Total orders', String(total));
24
+ this.stat(summary, 'Regions', String(REGIONS.length));
25
+ }
26
+
27
+ chart(page) {
28
+ const chart = page.figure({class: 'card'});
29
+ const svg = chart.svg({viewBox: '0 0 520 230', role: 'img', aria_labelledby: 'report-chart-title', class: 'example-art'});
30
+ svg.title('Orders by region: North 12, South 9, West 15', {id: 'report-chart-title'});
31
+ REGIONS.forEach(([region, value, color], index) => {
32
+ const y = 28 + index * 68;
33
+ svg.text(region, {x: 12, y: y + 23, font_size: 17, fill: 'currentColor'});
34
+ svg.rect({x: 105, y, width: value * 23, height: 32, rx: 7, fill: color});
35
+ svg.text(String(value), {x: 112 + value * 23, y: y + 23, font_size: 17, fill: 'currentColor'});
36
+ });
37
+ chart.figcaption('The bar lengths and table cells use the same values.');
38
+ const legend = page.ul({class: 'report-legend'});
39
+ for (const [region, , color] of REGIONS) {
40
+ const item = legend.li();
41
+ item.span('■ ', {style: `color: ${color}`});
42
+ item.span(region);
43
+ }
44
+ }
45
+
46
+ table(page, total) {
47
+ const table = page.table();
48
+ table.caption('Exact figures');
49
+ const head = table.thead().tr();
50
+ head.th('Region', {scope: 'col'});
51
+ head.th('Orders', {scope: 'col'});
52
+ const body = table.tbody();
53
+ for (const [region, value] of REGIONS) {
54
+ const row = body.tr();
55
+ row.th(region, {scope: 'row'});
56
+ row.td(String(value));
57
+ }
58
+ const foot = table.tfoot().tr();
59
+ foot.th('Total', {scope: 'row'});
60
+ foot.td(String(total));
61
+ }
62
+
63
+ stat(parent, label, value) {
64
+ const card = parent.div({class: 'card'});
65
+ card.p(label, {class: 'muted'});
66
+ card.p(value, {class: 'report-number'});
67
+ }
68
+ }
@@ -0,0 +1,9 @@
1
+ # 11 · Static report
2
+
3
+ A summary, SVG bar chart, visible legend and table from one dataset.
4
+
5
+ Page methods build each section. Chart lengths and exact figures derive from the same local values.
6
+
7
+ **Try it:** Add a region, then check summary, legend, chart and table.
8
+
9
+ **Limit:** The report has no data service or live refresh.
@@ -0,0 +1,5 @@
1
+ .complete-page { display: grid; gap: 2rem; }
2
+ .complete-header { display: grid; gap: .55rem; }
3
+ .complete-header nav { display: flex; flex-wrap: wrap; gap: 1rem; padding-top: .5rem; }
4
+ .complete-hero { display: grid; gap: 1rem; text-align: center; }
5
+ .complete-grid { grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); }
@@ -0,0 +1,120 @@
1
+ import {Page as BasePage} from '@gramlot/gramlot/page';
2
+
3
+ const SESSIONS = [
4
+ ['09:00', 'A first Source tree', 'Mira', 'Describe a page with semantic HTML.'],
5
+ ['10:30', 'Drawing with SVG', 'Theo', 'Build a small scene from SVG shapes.'],
6
+ ['13:00', 'Putting it together', 'Mira & Theo', 'Compose cards, a schedule and a summary.'],
7
+ ];
8
+ const SPEAKERS = [['Mira', 'Page authoring'], ['Theo', 'SVG composition']];
9
+ const QUESTIONS = [['Do I need a service?', 'This local example has no remote data.'],
10
+ ['Is the map interactive?', 'No. It is a schematic SVG illustration.']];
11
+
12
+ export class Page extends BasePage {
13
+ static title = 'A complete example page';
14
+ static css = ['/themes/gramlot-base/theme.css'];
15
+
16
+ main(root) {
17
+ const shell = root.div({class: 'example-page complete-page'});
18
+ this.header(shell);
19
+ const main = shell.main({class: 'stack'});
20
+ this.hero(main);
21
+ this.highlights(main);
22
+ this.schedule(main);
23
+ this.speakers(main);
24
+ this.map(main);
25
+ this.faq(main);
26
+ shell.footer().small('Gramlot HTML/SVG examples · Static content');
27
+ }
28
+
29
+ header(shell) {
30
+ const header = shell.header({class: 'complete-header'});
31
+ header.p('GRAMLOT FIELD NOTES', {class: 'eyebrow'});
32
+ header.h1('A day of small interfaces');
33
+ header.p('A composed page built from HTML and SVG Source.');
34
+ const nav = header.nav({aria_label: 'Page sections'});
35
+ for (const [label, target] of [['Highlights', 'highlights'], ['Schedule', 'schedule'],
36
+ ['Speakers', 'speakers'], ['Map', 'map'], ['FAQ', 'faq']]) nav.a(label, {href: `#${target}`});
37
+ }
38
+
39
+ hero(main) {
40
+ const hero = main.section({class: 'card complete-hero', aria_label: 'Illustration'});
41
+ const drawing = hero.svg({viewBox: '0 0 720 180', role: 'img', aria_labelledby: 'complete-art-title', class: 'example-art'});
42
+ drawing.title('Three connected steps', {id: 'complete-art-title'});
43
+ drawing.line({x1: 125, y1: 90, x2: 595, y2: 90, stroke: 'var(--gramlot-border)', stroke_width: 8});
44
+ for (const [x, label, color] of [[125, 'WRITE', 'var(--gramlot-action)'],
45
+ [360, 'COMPOSE', 'var(--gramlot-warning)'], [595, 'REVIEW', 'var(--gramlot-success)']]) {
46
+ const group = drawing.g({transform: `translate(${x} 90)`});
47
+ group.circle({cx: 0, cy: 0, r: 47, fill: color});
48
+ group.text(label, {x: 0, y: 6, text_anchor: 'middle', font_size: 15,
49
+ font_weight: 'bold', fill: 'var(--gramlot-background)'});
50
+ }
51
+ hero.p('Three lessons. One Source tree. A page you can read without scripts beyond the Gramlot runtime.');
52
+ }
53
+
54
+ highlights(main) {
55
+ const section = main.section({id: 'highlights', class: 'stack'});
56
+ section.h2('Highlights');
57
+ const cards = section.div({class: 'grid complete-grid'});
58
+ for (const [number, title, description] of [
59
+ ['01', 'Write', 'Use page methods to describe content.'],
60
+ ['02', 'Compose', 'Nest HTML elements and SVG.'],
61
+ ['03', 'Review', 'Inspect the result in either language.'],
62
+ ]) {
63
+ const card = cards.article({class: 'card stack'});
64
+ card.p(number, {class: 'eyebrow'});
65
+ card.h3(title);
66
+ card.p(description);
67
+ }
68
+ }
69
+
70
+ schedule(main) {
71
+ const section = main.section({id: 'schedule', class: 'stack'});
72
+ section.h2('Schedule');
73
+ const table = section.table();
74
+ table.caption('Three short sessions');
75
+ const heading = table.thead().tr();
76
+ for (const title of ['Time', 'Session', 'Speaker', 'Focus']) heading.th(title, {scope: 'col'});
77
+ const body = table.tbody();
78
+ for (const [time, title, speaker, focus] of SESSIONS) {
79
+ const row = body.tr();
80
+ row.th(time, {scope: 'row'});
81
+ for (const text of [title, speaker, focus]) row.td(text);
82
+ }
83
+ }
84
+
85
+ speakers(main) {
86
+ const section = main.section({id: 'speakers', class: 'stack'});
87
+ section.h2('Speakers');
88
+ const cards = section.div({class: 'grid complete-grid'});
89
+ for (const [name, specialty] of SPEAKERS) {
90
+ const card = cards.article({class: 'card'});
91
+ card.h3(name);
92
+ card.p(specialty);
93
+ }
94
+ }
95
+
96
+ map(main) {
97
+ const section = main.section({id: 'map', class: 'stack'});
98
+ section.h2('A schematic venue map');
99
+ const figure = section.figure({class: 'card'});
100
+ const drawing = figure.svg({viewBox: '0 0 600 210', role: 'img', aria_labelledby: 'venue-title', class: 'example-art'});
101
+ drawing.title('Entrance, studio and hall connected by a path', {id: 'venue-title'});
102
+ drawing.path({d: 'M80 110 H300 V65 H510', fill: 'none', stroke: 'var(--gramlot-action)', stroke_width: 8});
103
+ for (const [x, y, label] of [[80, 110, 'Entrance'], [300, 65, 'Studio'], [510, 65, 'Hall']]) {
104
+ const stop = drawing.g({transform: `translate(${x} ${y})`});
105
+ stop.circle({cx: 0, cy: 0, r: 15, fill: 'var(--gramlot-warning)'});
106
+ stop.text(label, {x: 0, y: 37, text_anchor: 'middle', font_size: 17, fill: 'currentColor'});
107
+ }
108
+ figure.figcaption('A diagram made of paths, circles and labels; it is not navigation.');
109
+ }
110
+
111
+ faq(main) {
112
+ const section = main.section({id: 'faq', class: 'stack'});
113
+ section.h2('Frequently asked questions');
114
+ for (const [question, answer] of QUESTIONS) {
115
+ const details = section.details({class: 'card'});
116
+ details.summary(question);
117
+ details.p(answer);
118
+ }
119
+ }
120
+ }
@@ -0,0 +1,9 @@
1
+ # 12 · A complete page
2
+
3
+ An event page with schedule, speakers, schematic map and FAQ.
4
+
5
+ Page methods divide the sections; nested loops compose rows, speaker cards and map labels from local data.
6
+
7
+ **Try it:** Add a session, speaker and FAQ, then add a map stop.
8
+
9
+ **Limit:** The event content is illustrative; there is no booking or routing service.
@@ -0,0 +1,3 @@
1
+ .live-toolbar { display: flex; flex-wrap: wrap; gap: .5rem; }
2
+ .live-items { display: grid; gap: .5rem; padding-inline-start: 1.5rem; }
3
+ .example-page svg { display: block; width: min(100%, 32rem); height: auto; }
@@ -0,0 +1,64 @@
1
+ import {Page as BasePage} from '@gramlot/gramlot/page';
2
+
3
+ // These browser actions change Source, never DOM elements.
4
+ const ACTIONS = {
5
+ "add": "window.gramlot.builder.wrapSource(window.gramlot.source.getNode('main.page.work.items')).li('A new item', {class: 'card'})",
6
+ "remove": "const items = window.gramlot.source.getItem('main.page.work.items'); const last = items.getNodes().at(-1); if (last) items.popNode(last.label)",
7
+ "clear": "window.gramlot.source.getItem('main.page.work.items').clear()",
8
+ "rename": "window.gramlot.source.getNode('main.page.heading').setValue('Source is alive!')",
9
+ "color": "window.gramlot.source.getNode('main.page.motion.scene.ball').setAttr({fill: 'var(--gramlot-warning)'})",
10
+ "stop": "window.gramlot.source.getItem('main.page').popNode('motion')"
11
+ };
12
+ const ANIMATION = `queueMicrotask(() => {
13
+ const app = window.gramlot;
14
+ const motion = app.source.getNode('main.page.motion');
15
+ const ball = app.source.getNode('main.page.motion.scene.ball');
16
+ const timer = setInterval(() => {
17
+ ball.setAttr({cx: 160 + 110 * Math.sin(performance.now() / 700)});
18
+ }, 50);
19
+ app.renderer.onDispose(motion, () => clearInterval(timer));
20
+ });`;
21
+
22
+ export class Page extends BasePage {
23
+ static title = 'Live Source playground';
24
+ static css = ['/themes/gramlot-base/theme.css'];
25
+
26
+ main(root) {
27
+ const page = root.main({node_label: 'page', class: 'example-page stack'});
28
+ page.h1('Live Source playground', {node_label: 'heading'});
29
+ page.p('Create and remove items, change text and watch SVG move. Every change goes through Source.');
30
+ this.workspace(page);
31
+ this.motion(page);
32
+ page.p('Reload to restore the initial page. Changes are local to this browser page.', {class: 'muted'});
33
+ page.script(ANIMATION);
34
+ }
35
+
36
+ button(root, title, action) {
37
+ root.button(title, {type: 'button', onclick: ACTIONS[action]});
38
+ }
39
+
40
+ workspace(page) {
41
+ const work = page.section({node_label: 'work', class: 'card stack'});
42
+ work.h2('Build a list live');
43
+ work.p('The first two items are authored by a loop. Add item uses the browser Builder to create another li.');
44
+ const toolbar = work.div({class: 'live-toolbar'});
45
+ for (const [title, action] of [['Add item', 'add'], ['Remove last', 'remove'], ['Clear list', 'clear'], ['Change heading', 'rename']]) {
46
+ this.button(toolbar, title, action);
47
+ }
48
+ const items = work.ul({node_label: 'items', class: 'live-items'});
49
+ for (const title of ['Explore the Source', 'Try a live change']) items.li(title, {class: 'card'});
50
+ }
51
+
52
+ motion(page) {
53
+ const motion = page.section({node_label: 'motion', class: 'card stack'});
54
+ motion.h2('An animated Source attribute');
55
+ motion.p("Every 50 ms, setInterval changes the circle's cx attribute in Source. Gramlot projects it into SVG.");
56
+ const scene = motion.svg({node_label: 'scene', viewBox: '0 0 320 100', role: 'img', aria_label: 'A circle moving from side to side'});
57
+ scene.line({x1: 30, y1: 50, x2: 290, y2: 50, stroke: 'var(--gramlot-action)', stroke_width: 2});
58
+ scene.circle({node_label: 'ball', cx: 160, cy: 50, r: 16, fill: 'var(--gramlot-action)'});
59
+ const toolbar = motion.div({class: 'live-toolbar'});
60
+ this.button(toolbar, 'Change ball color', 'color');
61
+ this.button(toolbar, 'Remove animation', 'stop');
62
+ motion.p('Removing this section also clears its interval through renderer.onDispose.', {class: 'muted'});
63
+ }
64
+ }
@@ -0,0 +1,18 @@
1
+ # 13 · Live Source playground
2
+
3
+ Try the buttons: add list items, remove the last one, clear the list and change
4
+ the heading. Builder.wrapSource creates new elements; Source node and Bag methods
5
+ update or remove them. The renderer updates only the affected DOM branches.
6
+
7
+ The SVG circle moves because a short script calls setInterval every 50 ms and
8
+ changes its Source cx attribute. Change its color while it moves. Remove animation
9
+ deletes the entire section and runs the renderer.onDispose callback, clearing the
10
+ timer. Disposing the page also clears it. Reload restores the initial content.
11
+
12
+ Python and JavaScript pages declare the same structure and browser actions.
13
+ Named Source paths make the target of each operation explicit. Methods divide
14
+ the page into sections and loops construct the initial items and buttons.
15
+
16
+ This example demonstrates live Source, not Data binding, persistence or server
17
+ synchronization. Its scripts/actions run in the hosted profiles and in the regenerated standalone
18
+ directory opened directly from disk, offline.
@@ -0,0 +1,10 @@
1
+ # HTML / SVG
2
+
3
+ Learn to build a page with Gramlot: each instruction adds an element, and nested instructions describe its structure.
4
+
5
+ Start with **Hello World**, then explore text, lists, tables and form controls. Continue with SVG shapes and combine them with HTML. The final examples use methods and loops to compose larger pages from simple parts.
6
+
7
+ Compare each preview with its code, then try the small change suggested in the example's notes. Python and JavaScript express the same ideas.
8
+
9
+ Finish with **Live Source playground**: change the page while it is running and
10
+ watch a small SVG animation driven by Source attributes. The live examples also run in the regenerated standalone directory, offline.