@tsparticles/template-portfolio 4.1.3 → 4.2.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.
- package/CHANGELOG.md +16 -0
- package/README.md +3 -0
- package/package.json +4 -3
- package/template/angular/package.json +35 -0
- package/template/angular/src/app/app.component.css +180 -0
- package/template/angular/src/app/app.component.html +53 -0
- package/template/angular/src/app/app.component.ts +47 -0
- package/template/angular-confetti/package.json +36 -0
- package/template/angular-confetti/src/app/app.component.css +180 -0
- package/template/angular-confetti/src/app/app.component.html +53 -0
- package/template/angular-confetti/src/app/app.component.ts +47 -0
- package/template/angular-fireworks/package.json +36 -0
- package/template/angular-fireworks/src/app/app.component.css +180 -0
- package/template/angular-fireworks/src/app/app.component.html +53 -0
- package/template/angular-fireworks/src/app/app.component.ts +47 -0
- package/template/astro/package.json +21 -0
- package/template/astro/src/pages/index.astro +131 -0
- package/template/ember/app/templates/application.hbs +61 -0
- package/template/ember/package.json +25 -0
- package/template/inferno/package.json +23 -0
- package/template/inferno/src/App.css +180 -0
- package/template/inferno/src/App.tsx +100 -0
- package/template/jquery/package.json +23 -0
- package/template/jquery/src/main.ts +33 -0
- package/template/jquery/src/style.css +180 -0
- package/template/lit/package.json +22 -0
- package/template/lit/src/my-app.ts +117 -0
- package/template/lit/src/style.css +180 -0
- package/template/nextjs/package.json +25 -0
- package/template/nextjs/src/app/page.tsx +94 -0
- package/template/nextjs/src/app/providers.tsx +20 -0
- package/template/nuxt2/package.json +21 -0
- package/template/nuxt2/pages/index.vue +124 -0
- package/template/nuxt3/app.vue +116 -0
- package/template/nuxt3/package.json +22 -0
- package/template/nuxt4/app.vue +116 -0
- package/template/nuxt4/package.json +22 -0
- package/template/preact/package.json +22 -0
- package/template/preact/src/App.css +180 -0
- package/template/preact/src/App.tsx +98 -0
- package/template/qwik/package.json +22 -0
- package/template/qwik/src/App.tsx +104 -0
- package/template/react/package.json +19 -0
- package/template/react/src/App.css +180 -0
- package/template/react/src/App.tsx +85 -0
- package/template/riot/package.json +23 -0
- package/template/riot/src/app.riot +121 -0
- package/template/solid/package.json +18 -0
- package/template/solid/src/App.tsx +99 -0
- package/template/solid/src/index.css +180 -0
- package/template/solid/src/main.tsx +9 -0
- package/template/stencil/package.json +21 -0
- package/template/stencil/src/components/app-home/app-home.tsx +109 -0
- package/template/svelte/package.json +20 -0
- package/template/svelte/src/App.svelte +289 -0
- package/template/svelte/src/main.ts +12 -0
- package/template/vanilla/LICENSE +21 -0
- package/template/vanilla/package.json +1 -1
- package/template/vue2/package.json +22 -0
- package/template/vue2/src/App.vue +273 -0
- package/template/vue3/package.json +19 -0
- package/template/vue3/src/App.vue +264 -0
- package/template/vue3/src/main.ts +14 -0
- package/template/webcomponents/package.json +21 -0
- package/template/webcomponents/src/main.ts +38 -0
- package/template/webcomponents/src/style.css +180 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<vue-particles id="tsparticles" :options="options" />
|
|
3
|
+
<nav id="navbar">
|
|
4
|
+
<a v-for="item in navItems" :key="item" :href="'#' + item.toLowerCase()" class="nav-link">{{ item }}</a>
|
|
5
|
+
</nav>
|
|
6
|
+
<section id="hero" class="section">
|
|
7
|
+
<div class="hero-content">
|
|
8
|
+
<h1>Hello, I'm <span class="highlight">Your Name</span></h1>
|
|
9
|
+
<p class="subtitle">Full-Stack Developer & Designer</p>
|
|
10
|
+
</div>
|
|
11
|
+
</section>
|
|
12
|
+
<section id="about" class="section content-section">
|
|
13
|
+
<div class="container">
|
|
14
|
+
<h2>About Me</h2>
|
|
15
|
+
<p>A passionate developer who loves building beautiful and performant web applications. Experienced in modern JavaScript frameworks, cloud infrastructure, and UI/UX design.</p>
|
|
16
|
+
</div>
|
|
17
|
+
</section>
|
|
18
|
+
<section id="projects" class="section content-section">
|
|
19
|
+
<div class="container">
|
|
20
|
+
<h2>Projects</h2>
|
|
21
|
+
<div class="project-grid">
|
|
22
|
+
<div v-for="p in projects" :key="p.title" class="project-card">
|
|
23
|
+
<h3>{{ p.title }}</h3>
|
|
24
|
+
<p>{{ p.desc }}</p>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</section>
|
|
29
|
+
<section id="skills" class="section content-section">
|
|
30
|
+
<div class="container">
|
|
31
|
+
<h2>Skills</h2>
|
|
32
|
+
<div v-for="s in skills" :key="s.name" class="skill-bar">
|
|
33
|
+
<span class="skill-name">{{ s.name }}</span>
|
|
34
|
+
<div class="bar"><div class="fill" :style="{ width: s.pct + '%' }"></div></div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</section>
|
|
38
|
+
<section id="contact" class="section content-section">
|
|
39
|
+
<div class="container">
|
|
40
|
+
<h2>Contact</h2>
|
|
41
|
+
<form id="contactForm" @submit="handleSubmit">
|
|
42
|
+
<input type="text" placeholder="Your Name" required />
|
|
43
|
+
<input type="email" placeholder="Your Email" required />
|
|
44
|
+
<textarea placeholder="Your Message" rows="5" required></textarea>
|
|
45
|
+
<button type="submit">Send Message</button>
|
|
46
|
+
</form>
|
|
47
|
+
</div>
|
|
48
|
+
</section>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<script lang="ts">
|
|
52
|
+
import Vue from "vue";
|
|
53
|
+
import configs from "@tsparticles/configs";
|
|
54
|
+
import type { ISourceOptions } from "@tsparticles/engine";
|
|
55
|
+
|
|
56
|
+
const keys = Object.keys(configs);
|
|
57
|
+
const randomKey = keys[Math.floor(Math.random() * keys.length)] as keyof typeof configs;
|
|
58
|
+
const options: ISourceOptions = {
|
|
59
|
+
...(configs[randomKey] as ISourceOptions),
|
|
60
|
+
fullScreen: { enable: true, zIndex: -1 },
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export default Vue.extend({
|
|
64
|
+
name: "App",
|
|
65
|
+
data() {
|
|
66
|
+
return {
|
|
67
|
+
options,
|
|
68
|
+
navItems: ["Home", "About", "Projects", "Skills", "Contact"],
|
|
69
|
+
projects: [
|
|
70
|
+
{ title: "Project One", desc: "A web application built with Vue 2 and TypeScript." },
|
|
71
|
+
{ title: "Project Two", desc: "Real-time dashboard with data visualization." },
|
|
72
|
+
{ title: "Project Three", desc: "Mobile-first e-commerce platform." },
|
|
73
|
+
],
|
|
74
|
+
skills: [
|
|
75
|
+
{ name: "TypeScript", pct: 90 },
|
|
76
|
+
{ name: "Vue 2", pct: 85 },
|
|
77
|
+
{ name: "Node.js", pct: 80 },
|
|
78
|
+
{ name: "CSS", pct: 88 },
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
methods: {
|
|
83
|
+
handleSubmit(e: Event) {
|
|
84
|
+
e.preventDefault();
|
|
85
|
+
alert("Message sent! (demo)");
|
|
86
|
+
(e.target as HTMLFormElement).reset();
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
</script>
|
|
91
|
+
|
|
92
|
+
<style>
|
|
93
|
+
* {
|
|
94
|
+
margin: 0;
|
|
95
|
+
padding: 0;
|
|
96
|
+
box-sizing: border-box;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
html {
|
|
100
|
+
scroll-behavior: smooth;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
body {
|
|
104
|
+
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
105
|
+
color: #fff;
|
|
106
|
+
background: #0a0a1a;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#navbar {
|
|
110
|
+
position: fixed;
|
|
111
|
+
top: 0;
|
|
112
|
+
width: 100%;
|
|
113
|
+
z-index: 100;
|
|
114
|
+
display: flex;
|
|
115
|
+
justify-content: center;
|
|
116
|
+
gap: 2rem;
|
|
117
|
+
padding: 1rem;
|
|
118
|
+
background: rgba(10, 10, 26, 0.8);
|
|
119
|
+
backdrop-filter: blur(10px);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.nav-link {
|
|
123
|
+
color: rgba(255, 255, 255, 0.7);
|
|
124
|
+
text-decoration: none;
|
|
125
|
+
font-size: 0.9em;
|
|
126
|
+
transition: color 0.2s;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.nav-link:hover {
|
|
130
|
+
color: #6c5ce7;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.section {
|
|
134
|
+
min-height: 100vh;
|
|
135
|
+
display: flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
justify-content: center;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#hero {
|
|
141
|
+
position: relative;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.hero-content {
|
|
145
|
+
text-align: center;
|
|
146
|
+
z-index: 10;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.hero-content h1 {
|
|
150
|
+
font-size: 3.5em;
|
|
151
|
+
margin-bottom: 0.5rem;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.highlight {
|
|
155
|
+
color: #6c5ce7;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.subtitle {
|
|
159
|
+
font-size: 1.3em;
|
|
160
|
+
opacity: 0.7;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.content-section {
|
|
164
|
+
padding: 6rem 1rem;
|
|
165
|
+
min-height: auto;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.container {
|
|
169
|
+
max-width: 900px;
|
|
170
|
+
width: 100%;
|
|
171
|
+
margin: 0 auto;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
h2 {
|
|
175
|
+
font-size: 2.2em;
|
|
176
|
+
margin-bottom: 2rem;
|
|
177
|
+
color: #6c5ce7;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
p {
|
|
181
|
+
line-height: 1.7;
|
|
182
|
+
opacity: 0.85;
|
|
183
|
+
font-size: 1.1em;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.project-grid {
|
|
187
|
+
display: grid;
|
|
188
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
189
|
+
gap: 1.5rem;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.project-card {
|
|
193
|
+
background: rgba(255, 255, 255, 0.05);
|
|
194
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
195
|
+
border-radius: 12px;
|
|
196
|
+
padding: 1.5rem;
|
|
197
|
+
transition: transform 0.2s, border-color 0.2s;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.project-card:hover {
|
|
201
|
+
transform: translateY(-4px);
|
|
202
|
+
border-color: #6c5ce7;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.project-card h3 {
|
|
206
|
+
margin-bottom: 0.5rem;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.skill-bar {
|
|
210
|
+
margin-bottom: 1.2rem;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.skill-name {
|
|
214
|
+
display: block;
|
|
215
|
+
margin-bottom: 0.3em;
|
|
216
|
+
font-size: 0.95em;
|
|
217
|
+
opacity: 0.8;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.bar {
|
|
221
|
+
height: 10px;
|
|
222
|
+
background: rgba(255, 255, 255, 0.1);
|
|
223
|
+
border-radius: 5px;
|
|
224
|
+
overflow: hidden;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.fill {
|
|
228
|
+
height: 100%;
|
|
229
|
+
background: linear-gradient(90deg, #6c5ce7, #a29bfe);
|
|
230
|
+
border-radius: 5px;
|
|
231
|
+
transition: width 1s ease;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
#contactForm {
|
|
235
|
+
display: flex;
|
|
236
|
+
flex-direction: column;
|
|
237
|
+
gap: 1rem;
|
|
238
|
+
max-width: 500px;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
#contactForm input,
|
|
242
|
+
#contactForm textarea {
|
|
243
|
+
padding: 0.8em 1em;
|
|
244
|
+
font-size: 1em;
|
|
245
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
246
|
+
border-radius: 8px;
|
|
247
|
+
background: rgba(255, 255, 255, 0.05);
|
|
248
|
+
color: #fff;
|
|
249
|
+
outline: none;
|
|
250
|
+
font-family: inherit;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
#contactForm input:focus,
|
|
254
|
+
#contactForm textarea:focus {
|
|
255
|
+
border-color: #6c5ce7;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
#contactForm button {
|
|
259
|
+
padding: 0.8em;
|
|
260
|
+
font-size: 1em;
|
|
261
|
+
font-weight: 600;
|
|
262
|
+
border: none;
|
|
263
|
+
border-radius: 8px;
|
|
264
|
+
background: #6c5ce7;
|
|
265
|
+
color: #fff;
|
|
266
|
+
cursor: pointer;
|
|
267
|
+
transition: background 0.2s;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
#contactForm button:hover {
|
|
271
|
+
background: #a29bfe;
|
|
272
|
+
}
|
|
273
|
+
</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{packageName}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vue-tsc --noEmit && vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"vue": "^3.5.32",
|
|
13
|
+
"@tsparticles/vue3": "^4.1.3"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@vitejs/plugin-vue": "^6.0.5",
|
|
17
|
+
"vue-tsc": "^3.2.6"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import configs from "@tsparticles/configs";
|
|
3
|
+
import type { ISourceOptions } from "@tsparticles/engine";
|
|
4
|
+
|
|
5
|
+
const keys = Object.keys(configs);
|
|
6
|
+
const randomKey = keys[Math.floor(Math.random() * keys.length)] as keyof typeof configs;
|
|
7
|
+
|
|
8
|
+
const options: ISourceOptions = {
|
|
9
|
+
...(configs[randomKey] as ISourceOptions),
|
|
10
|
+
fullScreen: { enable: true, zIndex: -1 },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const skills = [
|
|
14
|
+
{ name: "TypeScript", pct: 90 },
|
|
15
|
+
{ name: "React", pct: 85 },
|
|
16
|
+
{ name: "Node.js", pct: 80 },
|
|
17
|
+
{ name: "CSS", pct: 88 },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const projects = [
|
|
21
|
+
{ title: "Project One", desc: "A web application built with React and TypeScript." },
|
|
22
|
+
{ title: "Project Two", desc: "Real-time dashboard with data visualization." },
|
|
23
|
+
{ title: "Project Three", desc: "Mobile-first e-commerce platform." },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
function handleSubmit(e: Event) {
|
|
27
|
+
e.preventDefault();
|
|
28
|
+
alert("Message sent! (demo)");
|
|
29
|
+
(e.target as HTMLFormElement).reset();
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<vue-particles id="tsparticles" :options="options" />
|
|
35
|
+
<nav id="navbar">
|
|
36
|
+
<a v-for="item in ['Home', 'About', 'Projects', 'Skills', 'Contact']" :key="item" :href="'#' + item.toLowerCase()" class="nav-link">{{ item }}</a>
|
|
37
|
+
</nav>
|
|
38
|
+
<section id="hero" class="section">
|
|
39
|
+
<div class="hero-content">
|
|
40
|
+
<h1>Hello, I'm <span class="highlight">Your Name</span></h1>
|
|
41
|
+
<p class="subtitle">Full-Stack Developer & Designer</p>
|
|
42
|
+
</div>
|
|
43
|
+
</section>
|
|
44
|
+
<section id="about" class="section content-section">
|
|
45
|
+
<div class="container">
|
|
46
|
+
<h2>About Me</h2>
|
|
47
|
+
<p>A passionate developer who loves building beautiful and performant web applications. Experienced in modern JavaScript frameworks, cloud infrastructure, and UI/UX design.</p>
|
|
48
|
+
</div>
|
|
49
|
+
</section>
|
|
50
|
+
<section id="projects" class="section content-section">
|
|
51
|
+
<div class="container">
|
|
52
|
+
<h2>Projects</h2>
|
|
53
|
+
<div class="project-grid">
|
|
54
|
+
<div v-for="p in projects" :key="p.title" class="project-card">
|
|
55
|
+
<h3>{{ p.title }}</h3>
|
|
56
|
+
<p>{{ p.desc }}</p>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</section>
|
|
61
|
+
<section id="skills" class="section content-section">
|
|
62
|
+
<div class="container">
|
|
63
|
+
<h2>Skills</h2>
|
|
64
|
+
<div v-for="s in skills" :key="s.name" class="skill-bar">
|
|
65
|
+
<span class="skill-name">{{ s.name }}</span>
|
|
66
|
+
<div class="bar"><div class="fill" :style="{ width: s.pct + '%' }"></div></div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</section>
|
|
70
|
+
<section id="contact" class="section content-section">
|
|
71
|
+
<div class="container">
|
|
72
|
+
<h2>Contact</h2>
|
|
73
|
+
<form id="contactForm" @submit="handleSubmit">
|
|
74
|
+
<input type="text" placeholder="Your Name" required />
|
|
75
|
+
<input type="email" placeholder="Your Email" required />
|
|
76
|
+
<textarea placeholder="Your Message" rows="5" required></textarea>
|
|
77
|
+
<button type="submit">Send Message</button>
|
|
78
|
+
</form>
|
|
79
|
+
</div>
|
|
80
|
+
</section>
|
|
81
|
+
</template>
|
|
82
|
+
|
|
83
|
+
<style>
|
|
84
|
+
* {
|
|
85
|
+
margin: 0;
|
|
86
|
+
padding: 0;
|
|
87
|
+
box-sizing: border-box;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
html {
|
|
91
|
+
scroll-behavior: smooth;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
body {
|
|
95
|
+
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
96
|
+
color: #fff;
|
|
97
|
+
background: #0a0a1a;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
#navbar {
|
|
101
|
+
position: fixed;
|
|
102
|
+
top: 0;
|
|
103
|
+
width: 100%;
|
|
104
|
+
z-index: 100;
|
|
105
|
+
display: flex;
|
|
106
|
+
justify-content: center;
|
|
107
|
+
gap: 2rem;
|
|
108
|
+
padding: 1rem;
|
|
109
|
+
background: rgba(10, 10, 26, 0.8);
|
|
110
|
+
backdrop-filter: blur(10px);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.nav-link {
|
|
114
|
+
color: rgba(255, 255, 255, 0.7);
|
|
115
|
+
text-decoration: none;
|
|
116
|
+
font-size: 0.9em;
|
|
117
|
+
transition: color 0.2s;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.nav-link:hover {
|
|
121
|
+
color: #6c5ce7;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.section {
|
|
125
|
+
min-height: 100vh;
|
|
126
|
+
display: flex;
|
|
127
|
+
align-items: center;
|
|
128
|
+
justify-content: center;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
#hero {
|
|
132
|
+
position: relative;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.hero-content {
|
|
136
|
+
text-align: center;
|
|
137
|
+
z-index: 10;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.hero-content h1 {
|
|
141
|
+
font-size: 3.5em;
|
|
142
|
+
margin-bottom: 0.5rem;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.highlight {
|
|
146
|
+
color: #6c5ce7;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.subtitle {
|
|
150
|
+
font-size: 1.3em;
|
|
151
|
+
opacity: 0.7;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.content-section {
|
|
155
|
+
padding: 6rem 1rem;
|
|
156
|
+
min-height: auto;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.container {
|
|
160
|
+
max-width: 900px;
|
|
161
|
+
width: 100%;
|
|
162
|
+
margin: 0 auto;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
h2 {
|
|
166
|
+
font-size: 2.2em;
|
|
167
|
+
margin-bottom: 2rem;
|
|
168
|
+
color: #6c5ce7;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
p {
|
|
172
|
+
line-height: 1.7;
|
|
173
|
+
opacity: 0.85;
|
|
174
|
+
font-size: 1.1em;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.project-grid {
|
|
178
|
+
display: grid;
|
|
179
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
180
|
+
gap: 1.5rem;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.project-card {
|
|
184
|
+
background: rgba(255, 255, 255, 0.05);
|
|
185
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
186
|
+
border-radius: 12px;
|
|
187
|
+
padding: 1.5rem;
|
|
188
|
+
transition: transform 0.2s, border-color 0.2s;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.project-card:hover {
|
|
192
|
+
transform: translateY(-4px);
|
|
193
|
+
border-color: #6c5ce7;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.project-card h3 {
|
|
197
|
+
margin-bottom: 0.5rem;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.skill-bar {
|
|
201
|
+
margin-bottom: 1.2rem;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.skill-name {
|
|
205
|
+
display: block;
|
|
206
|
+
margin-bottom: 0.3em;
|
|
207
|
+
font-size: 0.95em;
|
|
208
|
+
opacity: 0.8;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.bar {
|
|
212
|
+
height: 10px;
|
|
213
|
+
background: rgba(255, 255, 255, 0.1);
|
|
214
|
+
border-radius: 5px;
|
|
215
|
+
overflow: hidden;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.fill {
|
|
219
|
+
height: 100%;
|
|
220
|
+
background: linear-gradient(90deg, #6c5ce7, #a29bfe);
|
|
221
|
+
border-radius: 5px;
|
|
222
|
+
transition: width 1s ease;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
#contactForm {
|
|
226
|
+
display: flex;
|
|
227
|
+
flex-direction: column;
|
|
228
|
+
gap: 1rem;
|
|
229
|
+
max-width: 500px;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
#contactForm input,
|
|
233
|
+
#contactForm textarea {
|
|
234
|
+
padding: 0.8em 1em;
|
|
235
|
+
font-size: 1em;
|
|
236
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
237
|
+
border-radius: 8px;
|
|
238
|
+
background: rgba(255, 255, 255, 0.05);
|
|
239
|
+
color: #fff;
|
|
240
|
+
outline: none;
|
|
241
|
+
font-family: inherit;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
#contactForm input:focus,
|
|
245
|
+
#contactForm textarea:focus {
|
|
246
|
+
border-color: #6c5ce7;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
#contactForm button {
|
|
250
|
+
padding: 0.8em;
|
|
251
|
+
font-size: 1em;
|
|
252
|
+
font-weight: 600;
|
|
253
|
+
border: none;
|
|
254
|
+
border-radius: 8px;
|
|
255
|
+
background: #6c5ce7;
|
|
256
|
+
color: #fff;
|
|
257
|
+
cursor: pointer;
|
|
258
|
+
transition: background 0.2s;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
#contactForm button:hover {
|
|
262
|
+
background: #a29bfe;
|
|
263
|
+
}
|
|
264
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createApp } from "vue";
|
|
2
|
+
import Particles from "@tsparticles/vue3";
|
|
3
|
+
import { loadSlim } from "@tsparticles/slim";
|
|
4
|
+
import App from "./App.vue";
|
|
5
|
+
|
|
6
|
+
const app = createApp(App);
|
|
7
|
+
|
|
8
|
+
app.use(Particles, {
|
|
9
|
+
init: async (engine) => {
|
|
10
|
+
await loadSlim(engine);
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
app.mount("#app");
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{packageName}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "{{version}}",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc && vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@tsparticles/webcomponents": "^4.1.3",
|
|
13
|
+
"@tsparticles/engine": "^4.0.0",
|
|
14
|
+
"@tsparticles/slim": "^4.0.0",
|
|
15
|
+
"@tsparticles/configs": "^4.0.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"typescript": "~5.8.0",
|
|
19
|
+
"vite": "^7.0.0"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import "./style.css";
|
|
2
|
+
import { ParticlesBase } from "@tsparticles/webcomponents";
|
|
3
|
+
import { loadSlim } from "@tsparticles/slim";
|
|
4
|
+
import configs from "@tsparticles/configs";
|
|
5
|
+
import type { ISourceOptions } from "@tsparticles/engine";
|
|
6
|
+
|
|
7
|
+
(async () => {
|
|
8
|
+
await ParticlesBase.init(async (engine) => {
|
|
9
|
+
await loadSlim(engine);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const keys = Object.keys(configs);
|
|
13
|
+
const randomKey = keys[Math.floor(Math.random() * keys.length)] as keyof typeof configs;
|
|
14
|
+
const options: ISourceOptions = {
|
|
15
|
+
...configs[randomKey],
|
|
16
|
+
fullScreen: { enable: true, zIndex: -1 },
|
|
17
|
+
} as ISourceOptions;
|
|
18
|
+
|
|
19
|
+
const particlesEl = document.createElement("ts-particles");
|
|
20
|
+
particlesEl.id = "tsparticles";
|
|
21
|
+
(particlesEl as unknown as Record<string, unknown>).options = options;
|
|
22
|
+
document.body.appendChild(particlesEl);
|
|
23
|
+
|
|
24
|
+
document.querySelectorAll(".nav-link").forEach((link) => {
|
|
25
|
+
link.addEventListener("click", (e) => {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
const target = document.querySelector((e.target as HTMLAnchorElement).getAttribute("href")!);
|
|
28
|
+
if (target) target.scrollIntoView({ behavior: "smooth" });
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const contactForm = document.getElementById("contactForm") as HTMLFormElement;
|
|
33
|
+
contactForm.addEventListener("submit", (e) => {
|
|
34
|
+
e.preventDefault();
|
|
35
|
+
alert("Message sent! (demo)");
|
|
36
|
+
contactForm.reset();
|
|
37
|
+
});
|
|
38
|
+
})();
|