@blocklet/component-studio-cli 0.4.134

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 (84) hide show
  1. package/README.md +84 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +11 -0
  4. package/dist/commands/add.d.ts +2 -0
  5. package/dist/commands/add.js +140 -0
  6. package/dist/commands/component-studio.d.ts +2 -0
  7. package/dist/commands/component-studio.js +70 -0
  8. package/dist/commands/dev.d.ts +2 -0
  9. package/dist/commands/dev.js +87 -0
  10. package/dist/commands/init.d.ts +2 -0
  11. package/dist/commands/init.js +114 -0
  12. package/dist/commands/migrate.d.ts +2 -0
  13. package/dist/commands/migrate.js +177 -0
  14. package/dist/commands/update.d.ts +2 -0
  15. package/dist/commands/update.js +22 -0
  16. package/dist/utils/display-logo.d.ts +2 -0
  17. package/dist/utils/display-logo.js +24 -0
  18. package/dist/utils/helper.d.ts +97 -0
  19. package/dist/utils/helper.js +479 -0
  20. package/package.json +58 -0
  21. package/templates/add/components/HelloWorld/@metadata.json +433 -0
  22. package/templates/add/components/HelloWorld/@template.json +4 -0
  23. package/templates/add/components/HelloWorld/index.tsx +560 -0
  24. package/templates/add/tools/cursor-rules/.cursor/rules/@metadata-json.mdc +549 -0
  25. package/templates/add/tools/cursor-rules/.cursor/rules/component-studio.mdc +138 -0
  26. package/templates/add/tools/cursor-rules/.cursor/rules/index-tsx.mdc +192 -0
  27. package/templates/add/tools/cursor-rules/@template.json +4 -0
  28. package/templates/init/0-basic/@template.json +4 -0
  29. package/templates/init/0-basic/README.md +30 -0
  30. package/templates/init/0-basic/package.json +26 -0
  31. package/templates/init/0-basic/src/HelloWorld/@metadata.json +433 -0
  32. package/templates/init/0-basic/src/HelloWorld/index.tsx +560 -0
  33. package/templates/init/1-professional/@template.json +4 -0
  34. package/templates/init/1-professional/README.md +75 -0
  35. package/templates/init/1-professional/biome.json +36 -0
  36. package/templates/init/1-professional/global.d.ts +131 -0
  37. package/templates/init/1-professional/package.json +73 -0
  38. package/templates/init/1-professional/scripts/bump-version.mjs +35 -0
  39. package/templates/init/1-professional/src/atoms/AnimationWrapper.tsx +95 -0
  40. package/templates/init/1-professional/src/atoms/ArrayTable.tsx +114 -0
  41. package/templates/init/1-professional/src/atoms/Card.tsx +52 -0
  42. package/templates/init/1-professional/src/atoms/ContentWrapper.tsx +72 -0
  43. package/templates/init/1-professional/src/atoms/CopyrightFooter.tsx +31 -0
  44. package/templates/init/1-professional/src/atoms/DataDisplays.tsx +157 -0
  45. package/templates/init/1-professional/src/atoms/GradientTitle.tsx +64 -0
  46. package/templates/init/1-professional/src/atoms/Logo.tsx +58 -0
  47. package/templates/init/1-professional/src/atoms/index.ts +27 -0
  48. package/templates/init/1-professional/src/components/HelloWorld/@metadata.json +433 -0
  49. package/templates/init/1-professional/src/components/HelloWorld/index.tsx +224 -0
  50. package/templates/init/1-professional/src/type.d.ts +42 -0
  51. package/templates/init/1-professional/src/utils/index.ts +1 -0
  52. package/templates/init/1-professional/tsconfig.json +102 -0
  53. package/templates/init/1-professional/version +1 -0
  54. package/templates/init/2-blank/@template.json +4 -0
  55. package/templates/init/2-blank/README.md +27 -0
  56. package/templates/init/2-blank/package.json +26 -0
  57. package/templates/workspace/.component-studio-runtime/_theme.tsx +4 -0
  58. package/templates/workspace/.editorconfig +23 -0
  59. package/templates/workspace/.env +1 -0
  60. package/templates/workspace/.init-component-studio +0 -0
  61. package/templates/workspace/LICENSE +13 -0
  62. package/templates/workspace/README.md +127 -0
  63. package/templates/workspace/api/dev.ts +21 -0
  64. package/templates/workspace/api/src/index.ts +50 -0
  65. package/templates/workspace/api/src/libs/auth.ts +17 -0
  66. package/templates/workspace/api/src/libs/env.ts +6 -0
  67. package/templates/workspace/api/src/libs/logger.ts +3 -0
  68. package/templates/workspace/api/src/routes/index.ts +12 -0
  69. package/templates/workspace/api/third.d.ts +17 -0
  70. package/templates/workspace/biome.json +36 -0
  71. package/templates/workspace/blocklet.md +8 -0
  72. package/templates/workspace/blocklet.yml +58 -0
  73. package/templates/workspace/index.html +15 -0
  74. package/templates/workspace/logo.png +0 -0
  75. package/templates/workspace/package.json +125 -0
  76. package/templates/workspace/pnpm-workspace.yaml +3 -0
  77. package/templates/workspace/scripts/build-clean.mjs +6 -0
  78. package/templates/workspace/scripts/bump-version.mjs +39 -0
  79. package/templates/workspace/scripts/init-component-studio.mjs +36 -0
  80. package/templates/workspace/tsconfig.api.json +12 -0
  81. package/templates/workspace/tsconfig.json +102 -0
  82. package/templates/workspace/version +1 -0
  83. package/templates/workspace/vite-server.config.ts +39 -0
  84. package/templates/workspace/vite.config.ts +68 -0
@@ -0,0 +1,131 @@
1
+ declare module '@arcblock/ux/*';
2
+
3
+ interface ComponentMountPoint {
4
+ did: string;
5
+ name: string;
6
+ mountPoint: string;
7
+ status: 'running' | 'starting';
8
+ }
9
+
10
+ interface Window {
11
+ __discuss_kit_client_id__?: string;
12
+ blocklet: {
13
+ appId: string;
14
+ appPid: string;
15
+ appLogo: string;
16
+ prefix: string;
17
+ appUrl: string;
18
+ appName: string;
19
+ appDescription: string;
20
+ componentMountPoints: ComponentMountPoint[];
21
+ pageGroup: 'blog' | 'discussion' | 'doc' | 'bookmark' | undefined;
22
+ capabilities: Record<string, any>;
23
+ preferences: {
24
+ maxCommentLength: number;
25
+ allowDeleteComment: boolean;
26
+ allowEditComment: boolean;
27
+ allowImageComment: boolean;
28
+ maxDiscussionLength: number;
29
+ maxPinLimit: number;
30
+ maxFeaturedLimit: number;
31
+ discussMode: 'knowledge-base' | 'BBS';
32
+ postsViewMode: 'nested' | 'flat';
33
+ allowDeleteDiscussion: boolean;
34
+ allowEditDiscussion: boolean;
35
+ allowNewDiscussion: boolean;
36
+ allowLockDiscussion: boolean;
37
+ allowPublishBlog: boolean;
38
+ displayBlogComments: boolean;
39
+ displayLabelFilterOnBlogList: boolean;
40
+ blogListStyle: 'default';
41
+ displayAuthorOnBlogList: boolean;
42
+ displayLabelsOnBlogList: boolean;
43
+ displayPublishTimeFilterOnBlogList: boolean;
44
+ displayBoardsOnBlogList: boolean;
45
+ displaySortingOnBlogList: boolean;
46
+ displayAuthorOnBlog: boolean;
47
+ displayLabelsOnBlog: boolean;
48
+ displayBoardOnBlog: boolean;
49
+ displayBackButtonOnBlog: boolean;
50
+ displayConnectButtonOnBlog: boolean;
51
+ displayReactionOnBlog: boolean;
52
+ displayReplyButtonForAnonymousUsers: boolean;
53
+ enableNotificationBadge: boolean;
54
+ enableAISearch: boolean;
55
+ enableDiscussLog: boolean;
56
+ discussLogPosition: 'standard' | 'top';
57
+ enableDiscussPostLog: boolean;
58
+ enableDiscussCommentLog: boolean;
59
+ enableBlogLog: boolean;
60
+ blogLogPosition: 'standard' | 'top';
61
+ enableBlogPostLog: boolean;
62
+ enableBlogCommentLog: boolean;
63
+ enableDocsLog: boolean;
64
+ docsLogPosition: 'standard' | 'top';
65
+ enableDocsPostLog: boolean;
66
+ enableDocsCommentLog: boolean;
67
+ badgeList: any[];
68
+ socialShareButtonsDiscussion: boolean;
69
+ socialShareButtonsBlog: boolean;
70
+ socialShareButtonsDoc: boolean;
71
+ blogListTemplate: 'standard' | 'corp';
72
+ blogTemplate: 'standard' | 'corp';
73
+ primaryColor: string;
74
+ secondaryColor: string;
75
+ subscriptionPublicType: 'blur' | 'slice';
76
+ enableAssignmentDiscussion: boolean;
77
+ enableAssignmentBlog: boolean;
78
+ enableAssignmentDoc: boolean;
79
+
80
+ bannerTitleDiscussionEn: string;
81
+ bannerTitleDiscussionZh: string;
82
+ bannerDescDiscussionEn: string;
83
+ bannerDescDiscussionZh: string;
84
+
85
+ bannerTitleBlogEn: string;
86
+ bannerTitleBlogZh: string;
87
+ bannerDescBlogEn: string;
88
+ bannerDescBlogZh: string;
89
+
90
+ bannerTitleDocEn: string;
91
+ bannerTitleDocZh: string;
92
+ bannerDescDocEn: string;
93
+ bannerDescDocZh: string;
94
+
95
+ bannerTitleBookmarkEn: string;
96
+ bannerTitleBookmarkZh: string;
97
+ bannerDescBookmarkEn: string;
98
+ bannerDescBookmarkZh: string;
99
+
100
+ discussionsVisiblePostTypes: string[];
101
+
102
+ allowDonationOnDiscussion: boolean;
103
+ allowDonationOnBlog: boolean;
104
+ allowDonationOnDoc: boolean;
105
+ allowDonationOnBookmark: boolean;
106
+ allowDonationAmountCustom: boolean;
107
+ discussionDonationButtonText: string;
108
+ discussionDonationHistoryStyle: string;
109
+ discussionDonationMaximum: string;
110
+ discussionDonationMinimum: string;
111
+ discussionDonationPresets: string;
112
+
113
+ discussionEnabled: boolean;
114
+ blogEnabled: boolean;
115
+ docEnabled: boolean;
116
+ bookmarkEnabled: boolean;
117
+ chatEnabled: boolean;
118
+
119
+ assignmentAuthorizedPassports: string[];
120
+
121
+ translatorAgentAid?: string;
122
+ inlineTranslatorAgentAid?: string;
123
+ };
124
+ };
125
+
126
+ chatInWallet?: {
127
+ safeAreaInsetTop?: string;
128
+ };
129
+
130
+ uploaderRef?: any;
131
+ }
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "professional-component-studio",
3
+ "description": "A professional component studio project, with a simple `HelloWorld` component example, and engineered project structure",
4
+ "private": true,
5
+ "version": "0.1.0",
6
+ "engines": {
7
+ "npm": "please-use-pnpm",
8
+ "yarn": "please-use-pnpm",
9
+ "node": ">=18",
10
+ "pnpm": ">=9"
11
+ },
12
+ "scripts": {
13
+ "dev": "component-studio dev",
14
+ "start": "component-studio dev",
15
+ "lint": "biome check . && tsc --noEmit",
16
+ "lint:skip": "echo 'skip lint'",
17
+ "lint:fix": "biome check --apply .",
18
+ "format": "biome format --write .",
19
+ "prepare": "npx simple-git-hooks",
20
+ "bump-version": "zx --quiet scripts/bump-version.mjs",
21
+ "update:deps": "npx -y taze -r -n '/arcblock|ocap|abtnode|blocklet|did-connect|did-comment|nedb/' -w -f && pnpm install && pnpm run deduplicate",
22
+ "deduplicate": "pnpm dedupe"
23
+ },
24
+ "lint-staged": {
25
+ "*.{mjs,js,jsx,ts,tsx,css,less,scss,json,graphql}": [
26
+ "pnpm run format",
27
+ "pnpm run lint:skip"
28
+ ]
29
+ },
30
+ "browserslist": {
31
+ "production": [
32
+ ">0.2%",
33
+ "not dead",
34
+ "not op_mini all"
35
+ ],
36
+ "development": [
37
+ "last 1 chrome version",
38
+ "last 1 firefox version",
39
+ "last 1 safari version"
40
+ ]
41
+ },
42
+ "dependencies": {},
43
+ "devDependencies": {
44
+ "@biomejs/biome": "^1.9.4",
45
+ "@types/react": "^18.3.18",
46
+ "@types/react-dom": "^18.3.5",
47
+ "bumpp": "^9.9.3",
48
+ "lint-staged": "^15.3.0",
49
+ "react": "^18.3.1",
50
+ "react-dom": "^18.3.1",
51
+ "simple-git-hooks": "^2.11.1",
52
+ "typescript": "^5.7.2",
53
+ "zx": "^8.3.0"
54
+ },
55
+ "peerDependencies": {
56
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
57
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
58
+ },
59
+ "importSort": {
60
+ ".js, .jsx, .mjs": {
61
+ "parser": "babylon",
62
+ "style": "module"
63
+ },
64
+ ".ts, .tsx": {
65
+ "style": "module",
66
+ "parser": "typescript"
67
+ }
68
+ },
69
+ "simple-git-hooks": {
70
+ "pre-commit": "npx lint-staged"
71
+ },
72
+ "license": "MIT"
73
+ }
@@ -0,0 +1,35 @@
1
+ /* eslint-disable no-console */
2
+ import { execSync } from 'child_process';
3
+ import { $, chalk, fs } from 'zx';
4
+
5
+ async function main() {
6
+ execSync('bumpp --no-tag --no-commit --no-push package.json', { stdio: 'inherit' });
7
+
8
+ const { version } = await fs.readJSON('package.json');
9
+ await fs.writeFileSync('version', version);
10
+
11
+ let newChangelog = '';
12
+ try {
13
+ const gitRes = await $`git log --pretty=format:"- %s" "main"...HEAD`;
14
+ newChangelog = gitRes.stdout.trim();
15
+ } catch {
16
+ console.error(chalk.redBright('Could not get git log, please write changelog manually.'));
17
+ }
18
+
19
+ const now = new Date();
20
+ const currentDate = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;
21
+ const title = `## ${version} (${currentDate})`;
22
+
23
+ await fs.ensureFile('CHANGELOG.md');
24
+ const oldChangelog = await fs.readFile('CHANGELOG.md', 'utf8');
25
+ const changelog = [title, newChangelog, oldChangelog].filter((item) => !!item).join('\n\n');
26
+ await fs.writeFile('CHANGELOG.md', changelog);
27
+
28
+ console.log(`\nNow you can make adjustments to ${chalk.cyan('CHANGELOG.md')} . Then press enter to continue.`);
29
+
30
+ process.stdin.setRawMode(true);
31
+ process.stdin.resume();
32
+ process.stdin.on('data', process.exit.bind(process, 0));
33
+ }
34
+
35
+ main();
@@ -0,0 +1,95 @@
1
+ import React, { useEffect, useState, CSSProperties, ReactNode } from 'react';
2
+
3
+ export type AnimationType = 'fadeIn' | 'fadeSlideUp' | 'fadeSlideDown';
4
+
5
+ export interface AnimationWrapperProps {
6
+ children: ReactNode;
7
+ animationType: AnimationType;
8
+ delay?: string;
9
+ duration?: string;
10
+ style?: CSSProperties;
11
+ className?: string;
12
+ }
13
+
14
+ export const AnimationWrapper: React.FC<AnimationWrapperProps> = ({
15
+ children,
16
+ animationType,
17
+ delay = '0s',
18
+ duration = '0.7s',
19
+ style = {},
20
+ className,
21
+ }) => {
22
+ const [animateIn, setAnimateIn] = useState(false);
23
+
24
+ useEffect(() => {
25
+ // Trigger entrance animation after component mounts
26
+ const timer = setTimeout(() => {
27
+ setAnimateIn(true);
28
+ }, 50);
29
+
30
+ return () => clearTimeout(timer);
31
+ }, []);
32
+
33
+ // Base animation styles
34
+ const animationStyle: CSSProperties = {
35
+ opacity: animateIn ? 1 : 0,
36
+ transform: animateIn
37
+ ? 'translateY(0)'
38
+ : animationType === 'fadeSlideUp'
39
+ ? 'translateY(20px)'
40
+ : animationType === 'fadeSlideDown'
41
+ ? 'translateY(-20px)'
42
+ : 'translateY(0)',
43
+ transition: `transform ${duration} ease, opacity ${duration} ease`,
44
+ transitionDelay: delay,
45
+ ...style,
46
+ };
47
+
48
+ return (
49
+ <div className={className} style={animationStyle}>
50
+ {children}
51
+ </div>
52
+ );
53
+ };
54
+
55
+ // Adds required keyframes to document head
56
+ export const useAnimationKeyframes = () => {
57
+ useEffect(() => {
58
+ const styleEl = document.createElement('style');
59
+ styleEl.textContent = `
60
+ @keyframes fadeIn {
61
+ from { opacity: 0; }
62
+ to { opacity: 1; }
63
+ }
64
+
65
+ @keyframes fadeSlideDown {
66
+ from {
67
+ opacity: 0;
68
+ transform: translateY(-20px);
69
+ }
70
+ to {
71
+ opacity: 1;
72
+ transform: translateY(0);
73
+ }
74
+ }
75
+
76
+ @keyframes fadeSlideUp {
77
+ from {
78
+ opacity: 0;
79
+ transform: translateY(20px);
80
+ }
81
+ to {
82
+ opacity: 1;
83
+ transform: translateY(0);
84
+ }
85
+ }
86
+ `;
87
+ document.head.appendChild(styleEl);
88
+
89
+ return () => {
90
+ document.head.removeChild(styleEl);
91
+ };
92
+ }, []);
93
+ };
94
+
95
+ export default AnimationWrapper;
@@ -0,0 +1,114 @@
1
+ import React, { CSSProperties } from 'react';
2
+
3
+ export interface ArrayTableProps<T extends Record<string, any>> {
4
+ data: T[];
5
+ columns: {
6
+ key: keyof T;
7
+ header: string;
8
+ render?: (value: any, record: T) => React.ReactNode;
9
+ }[];
10
+ title?: string;
11
+ style?: CSSProperties;
12
+ className?: string;
13
+ animate?: boolean;
14
+ delay?: string;
15
+ }
16
+
17
+ export function ArrayTable<T extends Record<string, any>>({
18
+ data,
19
+ columns,
20
+ title = 'Array Data',
21
+ style = {},
22
+ className,
23
+ animate = true,
24
+ delay = '0.3s',
25
+ }: ArrayTableProps<T>) {
26
+ const containerStyle: CSSProperties = {
27
+ margin: '2.5rem 0',
28
+ ...(animate && {
29
+ opacity: 1,
30
+ transform: 'translateY(0)',
31
+ transition: 'transform 0.4s ease, opacity 0.4s ease',
32
+ transitionDelay: delay,
33
+ }),
34
+ background: 'rgba(255, 255, 255, 0.8)',
35
+ borderRadius: '16px',
36
+ padding: '1.5rem',
37
+ backdropFilter: 'blur(10px)',
38
+ boxShadow: '0 10px 30px rgba(0, 0, 0, 0.05), 0 1px 3px rgba(0, 0, 0, 0.03)',
39
+ border: '1px solid rgba(255, 255, 255, 0.7)',
40
+ ...style,
41
+ };
42
+
43
+ const headerStyle: CSSProperties = {
44
+ padding: '1rem 1.5rem',
45
+ textAlign: 'left',
46
+ borderBottom: '2px solid rgba(99, 102, 241, 0.2)',
47
+ color: '#4F46E5',
48
+ fontWeight: 600,
49
+ fontSize: '1.1rem',
50
+ };
51
+
52
+ const cellStyle: CSSProperties = {
53
+ padding: '1rem 1.5rem',
54
+ borderBottom: '1px solid rgba(229, 231, 235, 0.7)',
55
+ fontSize: '1.05rem',
56
+ };
57
+
58
+ return (
59
+ <div style={containerStyle} className={className}>
60
+ <h3
61
+ style={{
62
+ textAlign: 'center',
63
+ margin: '0 0 1.5rem 0',
64
+ color: '#4F46E5',
65
+ fontSize: '1.5rem',
66
+ fontWeight: 600,
67
+ }}>
68
+ {title}
69
+ </h3>
70
+ <div style={{ overflowX: 'auto' }}>
71
+ <table style={{ width: '100%', borderCollapse: 'separate', borderSpacing: '0' }}>
72
+ <thead>
73
+ <tr>
74
+ {columns.map((column, index) => (
75
+ <th key={index} style={headerStyle}>
76
+ {column.header}
77
+ </th>
78
+ ))}
79
+ </tr>
80
+ </thead>
81
+ <tbody>
82
+ {data.map((item, rowIndex) => (
83
+ <tr
84
+ key={rowIndex}
85
+ style={{
86
+ background: rowIndex % 2 === 0 ? 'rgba(255, 255, 255, 0.5)' : 'rgba(249, 250, 251, 0.5)',
87
+ transition: 'background-color 0.2s ease',
88
+ }}
89
+ onMouseOver={(e) => {
90
+ e.currentTarget.style.backgroundColor = 'rgba(238, 242, 255, 0.7)';
91
+ }}
92
+ onMouseOut={(e) => {
93
+ e.currentTarget.style.backgroundColor =
94
+ rowIndex % 2 === 0 ? 'rgba(255, 255, 255, 0.5)' : 'rgba(249, 250, 251, 0.5)';
95
+ }}>
96
+ {columns.map((column, colIndex) => (
97
+ <td key={colIndex} style={cellStyle}>
98
+ {column.render
99
+ ? column.render(item[column.key], item)
100
+ : item[column.key] !== undefined && item[column.key] !== null
101
+ ? String(item[column.key])
102
+ : '-'}
103
+ </td>
104
+ ))}
105
+ </tr>
106
+ ))}
107
+ </tbody>
108
+ </table>
109
+ </div>
110
+ </div>
111
+ );
112
+ }
113
+
114
+ export default ArrayTable;
@@ -0,0 +1,52 @@
1
+ import React, { CSSProperties } from 'react';
2
+
3
+ export interface CardProps {
4
+ children: React.ReactNode;
5
+ style?: CSSProperties;
6
+ background?: string;
7
+ transitionDelay?: string;
8
+ className?: string;
9
+ }
10
+
11
+ export const Card: React.FC<CardProps> = ({
12
+ children,
13
+ style,
14
+ background = 'linear-gradient(180deg, rgba(255, 255, 255, 0.9) 0%, rgba(240, 248, 255, 0.9) 100%)',
15
+ transitionDelay = '0s',
16
+ className,
17
+ }) => {
18
+ const cardStyle: CSSProperties = {
19
+ padding: '1.5rem',
20
+ borderRadius: '12px',
21
+ backdropFilter: 'blur(10px)',
22
+ boxShadow: '0 10px 30px rgba(0, 0, 0, 0.08), 0 6px 12px rgba(99, 102, 241, 0.1)',
23
+ transition: 'transform 0.3s ease, box-shadow 0.3s ease',
24
+ margin: '1rem',
25
+ flex: '1 1 300px',
26
+ minWidth: '250px',
27
+ border: '1px solid rgba(255, 255, 255, 0.3)',
28
+ background,
29
+ transitionDelay,
30
+ ...style,
31
+ };
32
+
33
+ const handleMouseOver = (e: React.MouseEvent<HTMLDivElement>) => {
34
+ const target = e.currentTarget;
35
+ target.style.transform = 'translateY(-5px)';
36
+ target.style.boxShadow = '0 15px 35px rgba(0, 0, 0, 0.1), 0 8px 15px rgba(99, 102, 241, 0.2)';
37
+ };
38
+
39
+ const handleMouseOut = (e: React.MouseEvent<HTMLDivElement>) => {
40
+ const target = e.currentTarget;
41
+ target.style.transform = 'translateY(0)';
42
+ target.style.boxShadow = '0 10px 30px rgba(0, 0, 0, 0.08), 0 6px 12px rgba(99, 102, 241, 0.1)';
43
+ };
44
+
45
+ return (
46
+ <div className={className} style={cardStyle} onMouseOver={handleMouseOver} onMouseOut={handleMouseOut}>
47
+ {children}
48
+ </div>
49
+ );
50
+ };
51
+
52
+ export default Card;
@@ -0,0 +1,72 @@
1
+ import React, { CSSProperties, ReactNode } from 'react';
2
+
3
+ import { useAnimationKeyframes } from './AnimationWrapper';
4
+
5
+ export interface ContentWrapperProps {
6
+ children: ReactNode;
7
+ style?: CSSProperties;
8
+ className?: string;
9
+ withDecoration?: boolean;
10
+ }
11
+
12
+ export const ContentWrapper: React.FC<ContentWrapperProps> = ({
13
+ children,
14
+ style = {},
15
+ className,
16
+ withDecoration = true,
17
+ }) => {
18
+ // Add animation keyframes to document
19
+ useAnimationKeyframes();
20
+
21
+ const containerStyle: CSSProperties = {
22
+ fontFamily: '"Inter", "SF Pro Display", -apple-system, BlinkMacSystemFont, sans-serif',
23
+ padding: '2.5rem',
24
+ maxWidth: '1200px',
25
+ margin: '0 auto',
26
+ background: 'linear-gradient(135deg, #f5f7ff 0%, #ffffff 100%)',
27
+ borderRadius: '20px',
28
+ boxShadow: '0 20px 60px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.05)',
29
+ overflow: 'hidden',
30
+ position: 'relative',
31
+ ...style,
32
+ };
33
+
34
+ return (
35
+ <div className={`content-wrapper ${className || ''}`} style={containerStyle}>
36
+ {/* Background decoration */}
37
+ {withDecoration && (
38
+ <>
39
+ <div
40
+ style={{
41
+ position: 'absolute',
42
+ top: 0,
43
+ right: 0,
44
+ width: '300px',
45
+ height: '300px',
46
+ borderRadius: '50%',
47
+ background: 'radial-gradient(circle, rgba(99, 102, 241, 0.1) 0%, rgba(99, 102, 241, 0) 70%)',
48
+ zIndex: 0,
49
+ }}
50
+ />
51
+ <div
52
+ style={{
53
+ position: 'absolute',
54
+ bottom: '-100px',
55
+ left: '-100px',
56
+ width: '400px',
57
+ height: '400px',
58
+ borderRadius: '50%',
59
+ background: 'radial-gradient(circle, rgba(249, 115, 22, 0.08) 0%, rgba(249, 115, 22, 0) 70%)',
60
+ zIndex: 0,
61
+ }}
62
+ />
63
+ </>
64
+ )}
65
+
66
+ {/* Content area */}
67
+ <div style={{ position: 'relative', zIndex: 1 }}>{children}</div>
68
+ </div>
69
+ );
70
+ };
71
+
72
+ export default ContentWrapper;
@@ -0,0 +1,31 @@
1
+ import React, { CSSProperties } from 'react';
2
+
3
+ export interface CopyrightFooterProps {
4
+ text: string;
5
+ style?: CSSProperties;
6
+ className?: string;
7
+ animate?: boolean;
8
+ }
9
+
10
+ export const CopyrightFooter: React.FC<CopyrightFooterProps> = ({ text, style = {}, className, animate = true }) => {
11
+ const baseStyle: CSSProperties = {
12
+ textAlign: 'center',
13
+ color: '#6B7280',
14
+ fontSize: '0.9rem',
15
+ marginTop: '3rem',
16
+ padding: '1rem 0',
17
+ borderTop: '1px solid rgba(229, 231, 235, 0.7)',
18
+ ...(animate && { animation: 'fadeIn 1.2s ease forwards' }),
19
+ opacity: animate ? 0.8 : 0.7,
20
+ marginBottom: '-2.5rem',
21
+ ...style,
22
+ };
23
+
24
+ return (
25
+ <div className={className} style={baseStyle}>
26
+ {text}
27
+ </div>
28
+ );
29
+ };
30
+
31
+ export default CopyrightFooter;