Adding the first glitch gallery
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
# __COMPONENT_DISPLAY_NAME__
|
||||
|
||||
Standalone Glitch University component scaffolded from the workspace standard.
|
||||
|
||||
## Commands
|
||||
|
||||
- `npm install`
|
||||
- `npm run dev`
|
||||
- `npm run build`
|
||||
- `npm run preview`
|
||||
|
||||
## Notes
|
||||
|
||||
- The packaged entrypoint is `src/index.tsx`
|
||||
- Dev-only styling lives in `src/dev-theme.css`
|
||||
- Host integration metadata lives in `glitch.manifest.json`
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"componentId": "__COMPONENT_ID__",
|
||||
"displayName": "__COMPONENT_DISPLAY_NAME__",
|
||||
"version": "1.0.0",
|
||||
"folderName": "__FOLDER_NAME__",
|
||||
"packageName": "@glitch-components/__COMPONENT_ID__",
|
||||
"entry": "dist/__BUNDLE_NAME__.js",
|
||||
"source": "src/index.tsx",
|
||||
"tags": [
|
||||
"glitch-component"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>__COMPONENT_DISPLAY_NAME__</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/dev.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "@glitch-components/__COMPONENT_ID__",
|
||||
"version": "1.0.0",
|
||||
"description": "__COMPONENT_DISPLAY_NAME__ glitch component for Glitch University",
|
||||
"type": "module",
|
||||
"main": "./dist/__BUNDLE_NAME__.js",
|
||||
"module": "./dist/__BUNDLE_NAME__.js",
|
||||
"types": "./src/types.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/__BUNDLE_NAME__.js",
|
||||
"types": "./src/types.ts"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"src/types.ts",
|
||||
"glitch.manifest.json"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc --noEmit && vite build",
|
||||
"preview": "vite preview",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.0",
|
||||
"@types/react-dom": "^19.2.0",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
"leva": "^0.10.1",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.2.4",
|
||||
"vite-plugin-css-injected-by-js": "^3.5.2"
|
||||
},
|
||||
"keywords": [
|
||||
"glitch",
|
||||
"mini-game",
|
||||
"react",
|
||||
"component"
|
||||
],
|
||||
"author": "Glitch.university",
|
||||
"license": "MIT"
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import type { CSSProperties } from 'react'
|
||||
import styles from './styles.module.css'
|
||||
import type { GlitchComponentProps } from './types'
|
||||
|
||||
interface ComponentParams {
|
||||
speed?: number
|
||||
primaryColor?: string
|
||||
}
|
||||
|
||||
export default function GlitchComponent({
|
||||
config,
|
||||
onComplete,
|
||||
onProgress,
|
||||
theme,
|
||||
className
|
||||
}: GlitchComponentProps) {
|
||||
const params = (config.params ?? {}) as ComponentParams
|
||||
const speed = params.speed ?? 1
|
||||
|
||||
const componentTheme = {
|
||||
'--component-bg': theme?.bg,
|
||||
'--component-bg-secondary': theme?.bgSecondary,
|
||||
'--component-text': theme?.text,
|
||||
'--component-text-muted': theme?.textMuted,
|
||||
'--component-primary': params.primaryColor ?? theme?.primary,
|
||||
'--component-accent': theme?.accent,
|
||||
'--component-border': theme?.border
|
||||
} as CSSProperties
|
||||
|
||||
function handleComplete() {
|
||||
onProgress?.(100)
|
||||
onComplete({
|
||||
success: true,
|
||||
score: Math.round(100 * speed),
|
||||
data: { completedAt: new Date().toISOString(), speed }
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={[styles.root, className].filter(Boolean).join(' ')} style={componentTheme}>
|
||||
<h2 className={styles.title}>__COMPONENT_DISPLAY_NAME__</h2>
|
||||
<p className={styles.copy}>
|
||||
Replace this scaffold UI with the actual glitch mechanic. Keep the contract and host token usage.
|
||||
</p>
|
||||
<div className={styles.row}>
|
||||
<span className={styles.badge}>Speed: {speed.toFixed(1)}</span>
|
||||
<button className={styles.button} onClick={() => onProgress?.(50)} type="button">
|
||||
Progress 50%
|
||||
</button>
|
||||
<button className={styles.buttonPrimary} onClick={handleComplete} type="button">
|
||||
Complete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Iceland&family=JetBrains+Mono:wght@400;600&family=Russo+One&display=swap");
|
||||
|
||||
:root {
|
||||
--color-bg: #0a0a0f;
|
||||
--color-bg-secondary: #12121a;
|
||||
--color-text: #e8e8ec;
|
||||
--color-text-muted: #9999a8;
|
||||
--color-primary: #6366f1;
|
||||
--color-primary-hover: #818cf8;
|
||||
--color-accent: #22d3ee;
|
||||
--color-accent-secondary: #a855f7;
|
||||
--color-border: #2a2a3a;
|
||||
--font-main: "Iceland", -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
--font-display: "Russo One", -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
--font-mono: "JetBrains Mono", "Iceland", monospace;
|
||||
--small-font: 1.875rem;
|
||||
--spacing-xs: 0.5rem;
|
||||
--spacing-sm: 1rem;
|
||||
--spacing-md: 2rem;
|
||||
--spacing-lg: 4rem;
|
||||
--spacing-xl: 6rem;
|
||||
--max-width: 1200px;
|
||||
--border-radius: 12px;
|
||||
--border-radius-sm: 8px;
|
||||
--transition: 0.2s ease;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background:
|
||||
radial-gradient(circle at top, rgb(99 102 241 / 18%), transparent 36%),
|
||||
linear-gradient(180deg, #080810, #0d1018);
|
||||
color: var(--color-text);
|
||||
font-family: var(--font-main);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import { Leva, useControls } from 'leva'
|
||||
import Component from './Component'
|
||||
import './dev-theme.css'
|
||||
import { metadata } from './index'
|
||||
|
||||
function buildLevaSchema() {
|
||||
return Object.entries(metadata.paramSchema).reduce((acc, [key, schema]) => {
|
||||
if (schema.type === 'range' || schema.type === 'number') {
|
||||
acc[key] = {
|
||||
value: schema.default as number,
|
||||
min: schema.min,
|
||||
max: schema.max,
|
||||
step: schema.step
|
||||
}
|
||||
return acc
|
||||
}
|
||||
|
||||
if (schema.type === 'select') {
|
||||
acc[key] = {
|
||||
value: schema.default,
|
||||
options:
|
||||
schema.options?.reduce<Record<string, string | number>>((map, option) => {
|
||||
map[option.label] = option.value
|
||||
return map
|
||||
}, {}) ?? {}
|
||||
}
|
||||
return acc
|
||||
}
|
||||
|
||||
acc[key] = schema.default
|
||||
return acc
|
||||
}, {} as Record<string, unknown>)
|
||||
}
|
||||
|
||||
function DevHarness() {
|
||||
const params = useControls(buildLevaSchema())
|
||||
|
||||
return (
|
||||
<div style={{ minHeight: '100vh', padding: '2rem' }}>
|
||||
<Leva collapsed={false} />
|
||||
<Component
|
||||
config={{
|
||||
id: 'dev',
|
||||
name: metadata.name,
|
||||
version: metadata.version,
|
||||
params
|
||||
}}
|
||||
theme={{
|
||||
primary: '#6366f1',
|
||||
accent: '#22d3ee',
|
||||
bg: '#0a0a0f',
|
||||
bgSecondary: '#12121a',
|
||||
text: '#e8e8ec',
|
||||
textMuted: '#9999a8',
|
||||
border: '#2a2a3a'
|
||||
}}
|
||||
onProgress={(percent) => {
|
||||
console.log('Progress:', percent)
|
||||
}}
|
||||
onComplete={(result) => {
|
||||
console.log('Complete:', result)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<DevHarness />
|
||||
</React.StrictMode>
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
import Component from './Component'
|
||||
import type { GlitchComponentMetadata } from './types'
|
||||
|
||||
export default Component
|
||||
|
||||
export const metadata: GlitchComponentMetadata = {
|
||||
name: '__COMPONENT_ID__',
|
||||
displayName: '__COMPONENT_DISPLAY_NAME__',
|
||||
version: '1.0.0',
|
||||
paramSchema: {
|
||||
speed: {
|
||||
type: 'range',
|
||||
label: 'Speed',
|
||||
default: 1,
|
||||
min: 0.1,
|
||||
max: 5,
|
||||
step: 0.1
|
||||
},
|
||||
primaryColor: {
|
||||
type: 'color',
|
||||
label: 'Primary Color',
|
||||
default: '#6366f1'
|
||||
}
|
||||
},
|
||||
defaultParams: {
|
||||
speed: 1,
|
||||
primaryColor: '#6366f1'
|
||||
}
|
||||
}
|
||||
|
||||
export type {
|
||||
GlitchComponentProps,
|
||||
GlitchComponentConfig,
|
||||
GlitchComponentResult,
|
||||
GlitchTheme,
|
||||
GlitchHostBridge
|
||||
} from './types'
|
||||
@@ -0,0 +1,85 @@
|
||||
.root {
|
||||
--component-bg: var(--color-bg, #0a0a0f);
|
||||
--component-bg-secondary: var(--color-bg-secondary, #12121a);
|
||||
--component-text: var(--color-text, #e8e8ec);
|
||||
--component-text-muted: var(--color-text-muted, #9999a8);
|
||||
--component-primary: var(--color-primary, #6366f1);
|
||||
--component-accent: var(--color-accent, #22d3ee);
|
||||
--component-border: var(--color-border, #2a2a3a);
|
||||
box-sizing: border-box;
|
||||
width: min(100%, 900px);
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-md, 2rem);
|
||||
border-radius: var(--border-radius, 12px);
|
||||
border: 1px solid var(--component-border);
|
||||
background:
|
||||
radial-gradient(circle at 20% 10%, color-mix(in srgb, var(--component-primary) 16%, transparent), transparent 45%),
|
||||
linear-gradient(180deg, var(--component-bg-secondary), var(--component-bg));
|
||||
color: var(--component-text);
|
||||
font-family: var(--font-main, system-ui, sans-serif);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.root,
|
||||
.root *,
|
||||
.root *::before,
|
||||
.root *::after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0 0 var(--spacing-xs, 0.5rem);
|
||||
color: var(--component-primary);
|
||||
font-family: var(--font-display, var(--font-main, system-ui, sans-serif));
|
||||
font-size: clamp(1.2rem, 2vw, 1.75rem);
|
||||
}
|
||||
|
||||
.copy {
|
||||
margin: 0 0 var(--spacing-sm, 1rem);
|
||||
color: var(--component-text-muted);
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-xs, 0.5rem);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.badge {
|
||||
border: 1px solid var(--component-border);
|
||||
border-radius: var(--border-radius-sm, 8px);
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-family: var(--font-mono, monospace);
|
||||
background: color-mix(in srgb, var(--component-primary) 8%, transparent);
|
||||
}
|
||||
|
||||
.button,
|
||||
.buttonPrimary {
|
||||
border-radius: var(--border-radius-sm, 8px);
|
||||
border: 1px solid var(--component-border);
|
||||
padding: 0.65rem 0.9rem;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
transition:
|
||||
transform var(--transition, 0.2s ease),
|
||||
opacity var(--transition, 0.2s ease),
|
||||
border-color var(--transition, 0.2s ease);
|
||||
}
|
||||
|
||||
.button {
|
||||
background: var(--component-bg-secondary);
|
||||
color: var(--component-text);
|
||||
}
|
||||
|
||||
.buttonPrimary {
|
||||
background: var(--component-primary);
|
||||
border-color: color-mix(in srgb, var(--component-primary) 65%, white);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.button:hover,
|
||||
.buttonPrimary:hover {
|
||||
opacity: 0.95;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
export interface GlitchComponentConfig {
|
||||
id: string
|
||||
name: string
|
||||
version: string
|
||||
params: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface GlitchHostBridge {
|
||||
playSound?: (id: string, payload?: Record<string, unknown>) => void
|
||||
stopSound?: (id: string, payload?: Record<string, unknown>) => void
|
||||
emit?: (type: string, payload?: unknown) => void
|
||||
}
|
||||
|
||||
export interface GlitchTheme {
|
||||
primary: string
|
||||
accent: string
|
||||
bg: string
|
||||
bgSecondary: string
|
||||
text: string
|
||||
textMuted: string
|
||||
border: string
|
||||
}
|
||||
|
||||
export interface GlitchComponentResult {
|
||||
success: boolean
|
||||
score?: number
|
||||
data?: unknown
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface GlitchComponentProps {
|
||||
config: GlitchComponentConfig
|
||||
onComplete: (result: GlitchComponentResult) => void
|
||||
onProgress?: (percent: number) => void
|
||||
theme?: GlitchTheme
|
||||
className?: string
|
||||
host?: GlitchHostBridge
|
||||
}
|
||||
|
||||
export interface ParamSchema {
|
||||
[key: string]: {
|
||||
type: 'number' | 'string' | 'boolean' | 'color' | 'select' | 'range'
|
||||
label?: string
|
||||
description?: string
|
||||
default: unknown
|
||||
options?: { value: string | number; label: string }[]
|
||||
min?: number
|
||||
max?: number
|
||||
step?: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface GlitchComponentMetadata {
|
||||
name: string
|
||||
displayName: string
|
||||
version: string
|
||||
paramSchema: ParamSchema
|
||||
defaultParams: Record<string, unknown>
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": [
|
||||
"ES2022",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"module": "ESNext",
|
||||
"types": [
|
||||
"vite/client"
|
||||
],
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { resolve } from 'path'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { defineConfig } from 'vite'
|
||||
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
|
||||
|
||||
export default defineConfig(({ mode }) => ({
|
||||
plugins: [react(), cssInjectedByJsPlugin()],
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/index.tsx'),
|
||||
name: '__LIB_GLOBAL_NAME__',
|
||||
fileName: '__BUNDLE_NAME__',
|
||||
formats: ['es']
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['react', 'react-dom', 'react/jsx-runtime'],
|
||||
output: {
|
||||
globals: {
|
||||
react: 'React',
|
||||
'react-dom': 'ReactDOM',
|
||||
'react/jsx-runtime': 'jsxRuntime'
|
||||
},
|
||||
assetFileNames: 'assets/[name][extname]'
|
||||
}
|
||||
},
|
||||
sourcemap: true,
|
||||
minify: mode === 'production'
|
||||
},
|
||||
server: {
|
||||
port: 3001,
|
||||
open: true
|
||||
}
|
||||
}))
|
||||
Reference in New Issue
Block a user