sunpeak 0.4.2 → 0.5.1

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 (60) hide show
  1. package/README.md +2 -2
  2. package/bin/sunpeak.js +1 -1
  3. package/dist/chatgpt/chatgpt-simulator-types.d.ts +1 -1
  4. package/dist/chatgpt/chatgpt-simulator.d.ts +4 -15
  5. package/dist/chatgpt/index.d.ts +1 -1
  6. package/dist/chatgpt/mock-openai.d.ts +4 -16
  7. package/dist/index.cjs +70 -42
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.js +70 -42
  10. package/dist/index.js.map +1 -1
  11. package/dist/mcp/index.cjs +45 -112
  12. package/dist/mcp/index.cjs.map +1 -1
  13. package/dist/mcp/index.d.ts +2 -1
  14. package/dist/mcp/index.js +45 -112
  15. package/dist/mcp/index.js.map +1 -1
  16. package/dist/mcp/server.d.ts +2 -4
  17. package/dist/mcp/types.d.ts +16 -62
  18. package/dist/providers/index.d.ts +1 -3
  19. package/dist/providers/openai/index.d.ts +7 -0
  20. package/dist/{chatgpt/openai-provider.d.ts → providers/openai/provider.d.ts} +1 -1
  21. package/dist/{chatgpt/openai-types.d.ts → providers/openai/types.d.ts} +3 -32
  22. package/dist/providers/types.d.ts +4 -5
  23. package/dist/runtime/index.d.ts +7 -0
  24. package/dist/runtime/provider-detection.d.ts +17 -0
  25. package/dist/types/index.d.ts +2 -1
  26. package/dist/types/runtime.d.ts +34 -0
  27. package/dist/types/simulation.d.ts +47 -0
  28. package/package.json +2 -2
  29. package/template/README.md +1 -1
  30. package/template/dev/main.tsx +6 -2
  31. package/template/mcp/server.ts +8 -9
  32. package/template/package.json +1 -1
  33. package/template/scripts/build-all.mjs +43 -0
  34. package/template/scripts/validate.mjs +16 -7
  35. package/template/src/components/album/albums.tsx +7 -7
  36. package/template/src/components/card/card.tsx +4 -0
  37. package/template/src/components/index.ts +1 -1
  38. package/template/src/components/resources/AlbumsResource.tsx +13 -0
  39. package/template/src/{App.tsx → components/resources/CounterResource.tsx} +5 -14
  40. package/template/src/components/{simulations/carousel-simulation.tsx → resources/PlacesResource.tsx} +11 -17
  41. package/template/src/components/resources/index.ts +3 -0
  42. package/template/src/index-albums.tsx +9 -0
  43. package/template/src/index-carousel.tsx +9 -0
  44. package/template/src/index-counter.tsx +9 -0
  45. package/template/src/simulations/albums-simulation.ts +157 -0
  46. package/template/src/simulations/carousel-simulation.ts +94 -0
  47. package/template/src/simulations/counter-simulation.ts +43 -0
  48. package/template/src/simulations/index.ts +11 -0
  49. package/template/src/simulations/simulation-configs.ts +23 -0
  50. package/template/src/simulations/simulations.ts +36 -0
  51. package/template/src/simulations/types.ts +12 -0
  52. package/template/vite.config.build.ts +20 -14
  53. package/dist/chatgpt/mcp-provider.d.ts +0 -25
  54. package/template/data/albums.json +0 -112
  55. package/template/data/places.json +0 -49
  56. package/template/src/components/simulations/albums-simulation.tsx +0 -20
  57. package/template/src/components/simulations/app-simulation.tsx +0 -13
  58. package/template/src/components/simulations/index.tsx +0 -14
  59. package/template/src/index.chatgpt.tsx +0 -8
  60. package/template/src/index.ts +0 -3
@@ -0,0 +1,9 @@
1
+ import './styles/globals.css';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { PlacesResource } from './components/resources/PlacesResource';
4
+
5
+ // Mount the PlacesResource
6
+ const root = document.getElementById('root');
7
+ if (root) {
8
+ createRoot(root).render(<PlacesResource />);
9
+ }
@@ -0,0 +1,9 @@
1
+ import './styles/globals.css';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { CounterResource } from './components/resources/CounterResource';
4
+
5
+ // Mount the CounterResource
6
+ const root = document.getElementById('root');
7
+ if (root) {
8
+ createRoot(root).render(<CounterResource />);
9
+ }
@@ -0,0 +1,157 @@
1
+ import type { SimulationConfig } from './types';
2
+
3
+ /**
4
+ * Server-safe configuration for the albums simulation.
5
+ * This file contains only metadata and doesn't import React components or CSS.
6
+ */
7
+
8
+ const albumsData = {
9
+ albums: [
10
+ {
11
+ id: "summer-escape",
12
+ title: "Summer Slice",
13
+ cover: "https://persistent.oaistatic.com/pizzaz/pizzaz-1.png",
14
+ photos: [
15
+ {
16
+ id: "s1",
17
+ title: "Waves",
18
+ url: "https://persistent.oaistatic.com/pizzaz/pizzaz-2.png"
19
+ },
20
+ {
21
+ id: "s2",
22
+ title: "Palm trees",
23
+ url: "https://persistent.oaistatic.com/pizzaz/pizzaz-3.png"
24
+ },
25
+ {
26
+ id: "s3",
27
+ title: "Sunset",
28
+ url: "https://persistent.oaistatic.com/pizzaz/pizzaz-6.png"
29
+ }
30
+ ]
31
+ },
32
+ {
33
+ id: "city-lights",
34
+ title: "Pepperoni Nights",
35
+ cover: "https://persistent.oaistatic.com/pizzaz/pizzaz-4.png",
36
+ photos: [
37
+ {
38
+ id: "c1",
39
+ title: "Downtown",
40
+ url: "https://persistent.oaistatic.com/pizzaz/pizzaz-5.png"
41
+ },
42
+ {
43
+ id: "c2",
44
+ title: "Neon",
45
+ url: "https://persistent.oaistatic.com/pizzaz/pizzaz-1.png"
46
+ },
47
+ {
48
+ id: "c3",
49
+ title: "Streets",
50
+ url: "https://persistent.oaistatic.com/pizzaz/pizzaz-2.png"
51
+ }
52
+ ]
53
+ },
54
+ {
55
+ id: "into-the-woods",
56
+ title: "Truffle Forest",
57
+ cover: "https://persistent.oaistatic.com/pizzaz/pizzaz-3.png",
58
+ photos: [
59
+ {
60
+ id: "n1",
61
+ title: "Forest path",
62
+ url: "https://persistent.oaistatic.com/pizzaz/pizzaz-6.png"
63
+ },
64
+ {
65
+ id: "n2",
66
+ title: "Misty",
67
+ url: "https://persistent.oaistatic.com/pizzaz/pizzaz-4.png"
68
+ },
69
+ {
70
+ id: "n3",
71
+ title: "Waterfall",
72
+ url: "https://persistent.oaistatic.com/pizzaz/pizzaz-5.png"
73
+ }
74
+ ]
75
+ },
76
+ {
77
+ id: "pizza-tour",
78
+ title: "Pizza tour",
79
+ cover: "https://persistent.oaistatic.com/pizzaz/pizzaz-1.png",
80
+ photos: [
81
+ {
82
+ id: "tonys-pizza-napoletana",
83
+ title: "Tony's Pizza Napoletana",
84
+ url: "https://persistent.oaistatic.com/pizzaz/pizzaz-2.png"
85
+ },
86
+ {
87
+ id: "golden-boy-pizza",
88
+ title: "Golden Boy Pizza",
89
+ url: "https://persistent.oaistatic.com/pizzaz/pizzaz-3.png"
90
+ },
91
+ {
92
+ id: "pizzeria-delfina-mission",
93
+ title: "Pizzeria Delfina (Mission)",
94
+ url: "https://persistent.oaistatic.com/pizzaz/pizzaz-6.png"
95
+ },
96
+ {
97
+ id: "ragazza",
98
+ title: "Ragazza",
99
+ url: "https://persistent.oaistatic.com/pizzaz/pizzaz-4.png"
100
+ },
101
+ {
102
+ id: "del-popolo",
103
+ title: "Del Popolo",
104
+ url: "https://persistent.oaistatic.com/pizzaz/pizzaz-5.png"
105
+ },
106
+ {
107
+ id: "square-pie-guys",
108
+ title: "Square Pie Guys",
109
+ url: "https://persistent.oaistatic.com/pizzaz/pizzaz-1.png"
110
+ },
111
+ {
112
+ id: "zero-zero",
113
+ title: "Zero Zero",
114
+ url: "https://persistent.oaistatic.com/pizzaz/pizzaz-2.png"
115
+ }
116
+ ]
117
+ }
118
+ ]
119
+ };
120
+
121
+ export const albumsSimulation: SimulationConfig = {
122
+ userMessage: 'Pizza time',
123
+
124
+ // MCP Tool protocol - official Tool type from MCP SDK used in ListTools response
125
+ tool: {
126
+ name: 'albums',
127
+ description: 'Show photo albums',
128
+ inputSchema: { type: 'object', properties: {}, additionalProperties: false } as const,
129
+ title: 'Show',
130
+ annotations: { readOnlyHint: true },
131
+ _meta: {
132
+ 'openai/outputTemplate': 'ui://widget/album.html',
133
+ 'openai/toolInvocation/invoking': 'Loading albums',
134
+ 'openai/toolInvocation/invoked': 'Album loaded',
135
+ 'openai/widgetAccessible': true,
136
+ 'openai/resultCanProduceWidget': true,
137
+ },
138
+ },
139
+
140
+ // MCP Resource protocol - official Resource type from MCP SDK used in ListResources response
141
+ // resource.name is used as the simulation identifier
142
+ // resource.title is used as the simulation display label
143
+ resource: {
144
+ uri: 'ui://widget/album.html',
145
+ name: 'albums',
146
+ title: 'Albums',
147
+ description: 'Show photo albums widget markup',
148
+ mimeType: 'text/html+skybridge',
149
+ _meta: {},
150
+ },
151
+
152
+ // MCP CallTool protocol - data for CallTool response
153
+ toolCall: {
154
+ structuredContent: albumsData,
155
+ _meta: {},
156
+ },
157
+ };
@@ -0,0 +1,94 @@
1
+ import type { SimulationConfig } from './types';
2
+
3
+ /**
4
+ * Server-safe configuration for the carousel simulation.
5
+ * This file contains only metadata and doesn't import React components or CSS.
6
+ */
7
+
8
+ const placesData = {
9
+ places: [
10
+ {
11
+ id: "1",
12
+ name: "Lady Bird Lake",
13
+ rating: 4.5,
14
+ category: "Waterfront",
15
+ location: "Austin",
16
+ image: "https://images.unsplash.com/photo-1520950237264-dfe336995c34?w=400&h=400&fit=crop",
17
+ description: "Scenic lake perfect for kayaking, paddleboarding, and trails."
18
+ },
19
+ {
20
+ id: "2",
21
+ name: "Texas State Capitol",
22
+ rating: 4.8,
23
+ category: "Historic Site",
24
+ location: "Austin",
25
+ image: "https://images.unsplash.com/photo-1664231978322-4d0b45c7027b?w=400&h=400&fit=crop",
26
+ description: "Stunning capitol building with free tours and beautiful grounds."
27
+ },
28
+ {
29
+ id: "3",
30
+ name: "The Paramount Theatre",
31
+ rating: 4.7,
32
+ category: "Architecture",
33
+ location: "Austin",
34
+ image: "https://images.unsplash.com/photo-1583097090970-4d3b940ea1a0?w=400&h=400&fit=crop",
35
+ description: "Century-old performance and movie theatre in the heart of downtown Austin."
36
+ },
37
+ {
38
+ id: "4",
39
+ name: "Zilker Park",
40
+ rating: 4.7,
41
+ category: "Park",
42
+ location: "Austin",
43
+ image: "https://images.unsplash.com/photo-1563828568124-f800803ba13c?w=400&h=400&fit=crop",
44
+ description: "Popular park with trails, sports fields, and Barton Springs Pool."
45
+ },
46
+ {
47
+ id: "5",
48
+ name: "South Congress Avenue",
49
+ rating: 4.6,
50
+ category: "Landmark",
51
+ location: "Austin",
52
+ image: "https://images.unsplash.com/photo-1588993608283-7f0eda4438be?w=400&h=400&fit=crop",
53
+ description: "Vibrant street with unique shops, restaurants, and live music."
54
+ }
55
+ ]
56
+ };
57
+
58
+ export const carouselSimulation: SimulationConfig = {
59
+ userMessage: 'Show me popular places to visit in Austin Texas',
60
+
61
+ // MCP Tool protocol - official Tool type from MCP SDK used in ListTools response
62
+ tool: {
63
+ name: 'carousel',
64
+ description: 'Show popular places to visit',
65
+ inputSchema: { type: 'object', properties: {}, additionalProperties: false } as const,
66
+ title: 'Show',
67
+ annotations: { readOnlyHint: true },
68
+ _meta: {
69
+ 'openai/outputTemplate': 'ui://widget/carousel.html',
70
+ 'openai/toolInvocation/invoking': 'Loading carousel',
71
+ 'openai/toolInvocation/invoked': 'Carousel loaded',
72
+ 'openai/widgetAccessible': true,
73
+ 'openai/resultCanProduceWidget': true,
74
+ },
75
+ },
76
+
77
+ // MCP Resource protocol - official Resource type from MCP SDK used in ListResources response
78
+ // resource.name is used as the simulation identifier
79
+ // resource.title is used as the simulation display label
80
+ resource: {
81
+ uri: 'ui://widget/carousel.html',
82
+ name: 'carousel',
83
+ title: 'Carousel',
84
+ description: 'Show popular places to visit widget markup',
85
+ mimeType: 'text/html+skybridge',
86
+ _meta: {},
87
+ },
88
+
89
+ // MCP CallTool protocol - data for CallTool response
90
+ toolCall: {
91
+ structuredContent: placesData,
92
+ _meta: {},
93
+ },
94
+ };
@@ -0,0 +1,43 @@
1
+ import type { SimulationConfig } from './types';
2
+
3
+ /**
4
+ * Server-safe configuration for the counter simulation.
5
+ * This file contains only metadata and doesn't import React components or CSS.
6
+ */
7
+ export const counterSimulation: SimulationConfig = {
8
+ userMessage: 'Help me count something',
9
+
10
+ // MCP Tool protocol - official Tool type from MCP SDK used in ListTools response
11
+ tool: {
12
+ name: 'counter',
13
+ description: 'Show a simple counter tool',
14
+ inputSchema: { type: 'object', properties: {}, additionalProperties: false } as const,
15
+ title: 'Show',
16
+ annotations: { readOnlyHint: true },
17
+ _meta: {
18
+ 'openai/outputTemplate': 'ui://widget/counter.html',
19
+ 'openai/toolInvocation/invoking': 'Counting beans',
20
+ 'openai/toolInvocation/invoked': 'Beans counted',
21
+ 'openai/widgetAccessible': true,
22
+ 'openai/resultCanProduceWidget': true,
23
+ },
24
+ },
25
+
26
+ // MCP Resource protocol - official Resource type from MCP SDK used in ListResources response
27
+ // resource.name is used as the simulation identifier
28
+ // resource.title is used as the simulation display label
29
+ resource: {
30
+ uri: 'ui://widget/counter.html',
31
+ name: 'counter',
32
+ title: 'Counter',
33
+ description: 'Show a simple counter tool widget markup',
34
+ mimeType: 'text/html+skybridge',
35
+ _meta: {},
36
+ },
37
+
38
+ // MCP CallTool protocol - data for CallTool response
39
+ toolCall: {
40
+ structuredContent: null,
41
+ _meta: {},
42
+ },
43
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Server-safe simulation configurations
3
+ * Safe to import in Node.js/MCP server contexts.
4
+ */
5
+ export { SIMULATIONS, type SimulationConfig, type SimulationName } from './simulation-configs';
6
+
7
+ /**
8
+ * Simulations - DO NOT import in Node.js/MCP server contexts!
9
+ * These include React components and CSS imports.
10
+ */
11
+ export { counterSimulation, albumsSimulation, carouselSimulation, simulations } from './simulations';
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Server-safe simulation configurations
3
+ *
4
+ * This file contains only metadata and can be safely imported in Node.js contexts
5
+ * (like MCP servers) without causing issues with CSS imports or React components.
6
+ */
7
+
8
+ import { counterSimulation } from './counter-simulation';
9
+ import { albumsSimulation } from './albums-simulation';
10
+ import { carouselSimulation } from './carousel-simulation';
11
+
12
+ export type { SimulationConfig } from './types';
13
+
14
+ /**
15
+ * Server-safe simulation configs - contains only metadata, no React components
16
+ */
17
+ export const SIMULATIONS = {
18
+ counter: counterSimulation,
19
+ albums: albumsSimulation,
20
+ carousel: carouselSimulation,
21
+ } as const;
22
+
23
+ export type SimulationName = keyof typeof SIMULATIONS;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Simulations - combines resources with their simulation configs
3
+ *
4
+ * This file creates simulations by pairing production-ready resources with their
5
+ * server-safe configs. Each simulation is used for development and testing.
6
+ *
7
+ * IMPORTANT: This file imports React components and CSS. Do not import this
8
+ * in Node.js/MCP server contexts. Use simulation-configs.ts instead.
9
+ */
10
+
11
+ import type { Simulation } from "sunpeak"
12
+ import { CounterResource } from "../components/resources/CounterResource"
13
+ import { AlbumsResource } from "../components/resources/AlbumsResource"
14
+ import { PlacesResource } from "../components/resources/PlacesResource"
15
+ import { SIMULATIONS } from "./simulation-configs"
16
+
17
+ export const counterSimulation: Simulation = {
18
+ ...SIMULATIONS.counter,
19
+ resourceComponent: CounterResource,
20
+ }
21
+
22
+ export const albumsSimulation: Simulation = {
23
+ ...SIMULATIONS.albums,
24
+ resourceComponent: AlbumsResource,
25
+ }
26
+
27
+ export const carouselSimulation: Simulation = {
28
+ ...SIMULATIONS.carousel,
29
+ resourceComponent: PlacesResource,
30
+ }
31
+
32
+ export const simulations: Simulation[] = [
33
+ counterSimulation,
34
+ carouselSimulation,
35
+ albumsSimulation,
36
+ ]
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Type definitions for simulations.
3
+ */
4
+
5
+ import type { Simulation } from 'sunpeak';
6
+
7
+ /**
8
+ * Server-safe simulation configuration.
9
+ * This is a Simulation without the resourceComponent, safe to use in Node.js/MCP server contexts.
10
+ * Contains metadata for both MCP server and simulation purposes.
11
+ */
12
+ export type SimulationConfig = Omit<Simulation, 'resourceComponent'>;
@@ -2,30 +2,35 @@ import { defineConfig } from 'vite';
2
2
  import react from '@vitejs/plugin-react';
3
3
  import path from 'path';
4
4
  import tailwindcss from '@tailwindcss/vite';
5
- import { readFileSync, writeFileSync, unlinkSync, existsSync } from 'fs';
5
+ import { readFileSync, writeFileSync, unlinkSync, existsSync, readdirSync } from 'fs';
6
6
 
7
7
  // Check if we're in the sunpeak workspace (directory is named "template")
8
8
  const isTemplate = path.basename(__dirname) === 'template';
9
9
  const parentSrc = path.resolve(__dirname, '../src');
10
10
 
11
- // Plugin to inline CSS into the JS bundle
11
+ // Plugin to inline CSS into the JS bundle for all output files
12
12
  function inlineCssPlugin() {
13
13
  return {
14
14
  name: 'inline-css',
15
15
  closeBundle() {
16
16
  const distDir = path.resolve(__dirname, 'dist/chatgpt');
17
- const jsFile = path.join(distDir, 'index.js');
18
17
  const cssFile = path.join(distDir, 'style.css');
19
18
 
20
- if (existsSync(cssFile) && existsSync(jsFile)) {
19
+ if (existsSync(cssFile)) {
21
20
  const css = readFileSync(cssFile, 'utf-8');
22
- const js = readFileSync(jsFile, 'utf-8');
23
-
24
- // Inject CSS at the start of the JS file
25
21
  const injectCss = `(function(){var s=document.createElement('style');s.textContent=${JSON.stringify(css)};document.head.appendChild(s);})();`;
26
- writeFileSync(jsFile, injectCss + js);
27
22
 
28
- // Remove the separate CSS file
23
+ // Find all .js files in the dist directory and inject CSS
24
+ const files = readdirSync(distDir);
25
+ files.forEach(file => {
26
+ if (file.endsWith('.js')) {
27
+ const jsFile = path.join(distDir, file);
28
+ const js = readFileSync(jsFile, 'utf-8');
29
+ writeFileSync(jsFile, injectCss + js);
30
+ }
31
+ });
32
+
33
+ // Remove the separate CSS file after injecting into all bundles
29
34
  unlinkSync(cssFile);
30
35
  }
31
36
  },
@@ -49,18 +54,19 @@ export default defineConfig({
49
54
  },
50
55
  build: {
51
56
  target: 'es2020',
57
+ outDir: 'dist/chatgpt',
58
+ emptyOutDir: false,
59
+ cssCodeSplit: false,
52
60
  lib: {
53
- entry: path.resolve(__dirname, 'src/index.chatgpt.tsx'),
61
+ entry: path.resolve(__dirname, process.env.ENTRY_FILE || 'src/index-app.tsx'),
54
62
  name: 'SunpeakApp',
55
63
  formats: ['iife'],
56
- fileName: () => 'index.js',
64
+ fileName: () => process.env.OUTPUT_FILE || 'app.js',
57
65
  },
58
- outDir: 'dist/chatgpt',
59
- cssCodeSplit: false,
60
66
  rollupOptions: {
61
67
  output: {
62
68
  inlineDynamicImports: true,
63
- assetFileNames: '[name][extname]',
69
+ assetFileNames: 'style.css',
64
70
  },
65
71
  },
66
72
  minify: true,
@@ -1,25 +0,0 @@
1
- import { Resource, Tool } from '@modelcontextprotocol/sdk/types.js';
2
- import { MCPProviderImplementation, WidgetDescriptorMeta, WidgetInvocationMeta } from '../mcp/types.js';
3
- /**
4
- * ChatGPT MCP provider implementation.
5
- */
6
- export declare class ChatGPTMCPProvider implements MCPProviderImplementation {
7
- getWidgetDescriptorMeta(): WidgetDescriptorMeta;
8
- getWidgetInvocationMeta(): WidgetInvocationMeta;
9
- readWidgetContent(distPath: string): string;
10
- getWidgetMimeType(): string;
11
- getWidgetResourceUri(): string;
12
- createTool(config: {
13
- name: string;
14
- description: string;
15
- inputSchema: Tool["inputSchema"];
16
- }): Tool;
17
- createResource(config: {
18
- name: string;
19
- description: string;
20
- }): Resource;
21
- }
22
- /**
23
- * Get the ChatGPT MCP provider instance.
24
- */
25
- export declare function getChatGPTMCPProvider(): MCPProviderImplementation;
@@ -1,112 +0,0 @@
1
- {
2
- "albums": [
3
- {
4
- "id": "summer-escape",
5
- "title": "Summer Slice",
6
- "cover": "https://persistent.oaistatic.com/pizzaz/pizzaz-1.png",
7
- "photos": [
8
- {
9
- "id": "s1",
10
- "title": "Waves",
11
- "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-2.png"
12
- },
13
- {
14
- "id": "s2",
15
- "title": "Palm trees",
16
- "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-3.png"
17
- },
18
- {
19
- "id": "s3",
20
- "title": "Sunset",
21
- "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-6.png"
22
- }
23
- ]
24
- },
25
- {
26
- "id": "city-lights",
27
- "title": "Pepperoni Nights",
28
- "cover": "https://persistent.oaistatic.com/pizzaz/pizzaz-4.png",
29
- "photos": [
30
- {
31
- "id": "c1",
32
- "title": "Downtown",
33
- "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-5.png"
34
- },
35
- {
36
- "id": "c2",
37
- "title": "Neon",
38
- "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-1.png"
39
- },
40
- {
41
- "id": "c3",
42
- "title": "Streets",
43
- "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-2.png"
44
- }
45
- ]
46
- },
47
- {
48
- "id": "into-the-woods",
49
- "title": "Truffle Forest",
50
- "cover": "https://persistent.oaistatic.com/pizzaz/pizzaz-3.png",
51
- "photos": [
52
- {
53
- "id": "n1",
54
- "title": "Forest path",
55
- "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-6.png"
56
- },
57
- {
58
- "id": "n2",
59
- "title": "Misty",
60
- "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-4.png"
61
- },
62
- {
63
- "id": "n3",
64
- "title": "Waterfall",
65
- "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-5.png"
66
- }
67
- ]
68
- },
69
- {
70
- "id": "pizza-tour",
71
- "title": "Pizza tour",
72
- "cover": "https://persistent.oaistatic.com/pizzaz/pizzaz-1.png",
73
- "photos": [
74
- {
75
- "id": "tonys-pizza-napoletana",
76
- "title": "Tony's Pizza Napoletana",
77
- "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-2.png"
78
- },
79
- {
80
- "id": "golden-boy-pizza",
81
- "title": "Golden Boy Pizza",
82
- "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-3.png"
83
- },
84
- {
85
- "id": "pizzeria-delfina-mission",
86
- "title": "Pizzeria Delfina (Mission)",
87
- "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-6.png"
88
- },
89
- {
90
- "id": "ragazza",
91
- "title": "Ragazza",
92
- "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-4.png"
93
- },
94
- {
95
- "id": "del-popolo",
96
- "title": "Del Popolo",
97
- "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-5.png"
98
- },
99
- {
100
- "id": "square-pie-guys",
101
- "title": "Square Pie Guys",
102
- "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-1.png"
103
- },
104
- {
105
- "id": "zero-zero",
106
- "title": "Zero Zero",
107
- "url": "https://persistent.oaistatic.com/pizzaz/pizzaz-2.png"
108
- }
109
- ]
110
- }
111
- ]
112
- }
@@ -1,49 +0,0 @@
1
- {
2
- "places": [
3
- {
4
- "id": "1",
5
- "name": "Lady Bird Lake",
6
- "rating": 4.5,
7
- "category": "Waterfront",
8
- "location": "Austin",
9
- "image": "https://images.unsplash.com/photo-1520950237264-dfe336995c34?w=400&h=400&fit=crop",
10
- "description": "Scenic lake perfect for kayaking, paddleboarding, and trails."
11
- },
12
- {
13
- "id": "2",
14
- "name": "Texas State Capitol",
15
- "rating": 4.8,
16
- "category": "Historic Site",
17
- "location": "Austin",
18
- "image": "https://images.unsplash.com/photo-1664231978322-4d0b45c7027b?w=400&h=400&fit=crop",
19
- "description": "Stunning capitol building with free tours and beautiful grounds."
20
- },
21
- {
22
- "id": "3",
23
- "name": "The Paramount Theatre",
24
- "rating": 4.7,
25
- "category": "Architecture",
26
- "location": "Austin",
27
- "image": "https://images.unsplash.com/photo-1583097090970-4d3b940ea1a0?w=400&h=400&fit=crop",
28
- "description": "Century-old performance and movie theatre in the heart of downtown Austin."
29
- },
30
- {
31
- "id": "4",
32
- "name": "Zilker Park",
33
- "rating": 4.7,
34
- "category": "Park",
35
- "location": "Austin",
36
- "image": "https://images.unsplash.com/photo-1563828568124-f800803ba13c?w=400&h=400&fit=crop",
37
- "description": "Popular park with trails, sports fields, and Barton Springs Pool."
38
- },
39
- {
40
- "id": "5",
41
- "name": "South Congress Avenue",
42
- "rating": 4.6,
43
- "category": "Landmark",
44
- "location": "Austin",
45
- "image": "https://images.unsplash.com/photo-1588993608283-7f0eda4438be?w=400&h=400&fit=crop",
46
- "description": "Vibrant street with unique shops, restaurants, and live music."
47
- }
48
- ]
49
- }