Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e45bb9506 | ||
|
|
64a2adbe1a | ||
|
|
99736db27f | ||
|
|
eb3310c537 | ||
|
|
ab2c2cf153 | ||
|
|
71b73a323a | ||
|
|
659023988e | ||
|
|
c2b9afa1a1 | ||
|
|
258dfde60e | ||
|
|
91ac907778 | ||
|
|
73da8740ee | ||
|
|
4ad0cc1366 | ||
|
|
15f2fb82a3 | ||
|
|
bd81b7c10c | ||
|
|
f6c99e949b | ||
|
|
dcde53f8ad | ||
|
|
d3aacf0d14 | ||
|
|
20be96f6f8 | ||
|
|
437892cdfa | ||
|
|
5065bb4763 | ||
|
|
baa63866f4 |
No files matched your search
@@ -18,7 +18,9 @@ updates:
|
||||
open-pull-requests-limit: 15
|
||||
|
||||
- package-ecosystem: npm
|
||||
directory: /ui
|
||||
# setup only for whole workspace to fix issues with package.lock,
|
||||
# SEE https://github.com/dependabot/dependabot-core/issues/4993#issuecomment-1739296001
|
||||
directory: /
|
||||
schedule:
|
||||
interval: monthly
|
||||
commit-message:
|
||||
@@ -29,18 +31,7 @@ updates:
|
||||
dev-deps:
|
||||
dependency-type: development
|
||||
open-pull-requests-limit: 15
|
||||
|
||||
- package-ecosystem: npm
|
||||
directory: /docs
|
||||
schedule:
|
||||
interval: monthly
|
||||
commit-message:
|
||||
prefix: "chore(deps/docs): "
|
||||
groups:
|
||||
deps:
|
||||
patterns:
|
||||
- "*"
|
||||
open-pull-requests-limit: 15
|
||||
versioning-strategy: increase
|
||||
|
||||
- package-ecosystem: docker
|
||||
directory: /
|
||||
|
||||
@@ -78,21 +78,12 @@ jobs:
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build UI
|
||||
id: build_ui
|
||||
run: |
|
||||
make build-frontend
|
||||
echo "ui_tarball=$(make -s pack-frontend)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Pack UI
|
||||
id: pack
|
||||
run: |
|
||||
SRC_FILE=$(pnpm pack -C ui/artalk --pack-destination ../.. | tail -n 1)
|
||||
|
||||
mkdir -p bin
|
||||
PKG_FILE="bin/artalk_frontend_nigthly_$(date +'%Y%m%d').tar.gz"
|
||||
|
||||
mv $SRC_FILE $PKG_FILE
|
||||
echo "pkg_file=$PKG_FILE" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Upload UI
|
||||
- name: Upload UI dist
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ui-src
|
||||
@@ -102,11 +93,12 @@ jobs:
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload UI package
|
||||
- name: Upload UI tarball (included in release)
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-ui-pkg
|
||||
path: ${{ steps.pack.outputs.pkg_file }}
|
||||
name: build-ui-tarball
|
||||
path: ${{ steps.build_ui.outputs.ui_tarball }}
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
#
|
||||
|
||||
@@ -49,6 +49,7 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
__debug*
|
||||
vite.config.ts.timestamp-*
|
||||
|
||||
# frontend
|
||||
node_modules
|
||||
|
||||
@@ -17,6 +17,7 @@ before:
|
||||
# install dependencies
|
||||
- make install
|
||||
- make build-frontend
|
||||
- make pack-frontend
|
||||
|
||||
# build multi-platform
|
||||
builds:
|
||||
|
||||
@@ -9,6 +9,7 @@ pnpm-lock.yaml
|
||||
**/auto-imports.d.ts
|
||||
**/components.d.ts
|
||||
**/typed-router.d.ts
|
||||
**/vite.config.ts.timestamp-*
|
||||
ui/artalk/src/api/v2.ts
|
||||
docs/swagger/swagger.json
|
||||
docs/swagger/swagger.yaml
|
||||
|
||||
@@ -183,6 +183,30 @@ pnpm check:publish
|
||||
|
||||
This will initiate the process of checking all public packages in the repository to ensure they are up to date on npm.
|
||||
|
||||
### Develop and Debug `@artalk/plugin-kit` and `eslint-plugin-artalk`
|
||||
|
||||
The monorepo will default to installing the latest version of `@artalk/plugin-kit` and `eslint-plugin-artalk` from NPM published packages. To develop and debug these packages, run the following commands from the root of the repository:
|
||||
|
||||
```sh
|
||||
pnpm link --global --dir ui/eslint-plugin-artalk
|
||||
pnpm link --global --dir ui/plugin-kit
|
||||
|
||||
pnpm link eslint-plugin-artalk
|
||||
pnpm link @artalk/plugin-kit
|
||||
```
|
||||
|
||||
After executing these commands, the `eslint-plugin-artalk` and `@artalk/plugin-kit` packages will be linked to the local development environment within the monorepo workspace. You can modify the source code located in the `ui/eslint-plugin-artalk` and `ui/plugin-kit` directories, and the changes will be automatically reflected in the monorepo workspace. If you wish to unlink the packages, run the following commands:
|
||||
|
||||
```sh
|
||||
pnpm unlink eslint-plugin-artalk
|
||||
pnpm unlink @artalk/plugin-kit
|
||||
|
||||
pnpm uninstall --global eslint-plugin-artalk
|
||||
pnpm uninstall --global @artalk/plugin-kit
|
||||
```
|
||||
|
||||
For more details, refer to the [pnpm link documentation](https://pnpm.io/cli/link).
|
||||
|
||||
## Docker Development
|
||||
|
||||
### Build Docker Image
|
||||
|
||||
@@ -22,7 +22,10 @@ build:
|
||||
$(PKG_NAME)
|
||||
|
||||
build-frontend:
|
||||
./scripts/build-frontend.sh
|
||||
@./scripts/build-frontend.sh
|
||||
|
||||
pack-frontend:
|
||||
@./scripts/pack-frontend.sh
|
||||
|
||||
build-debug:
|
||||
@echo "Building Artalk for debugging..."
|
||||
@@ -73,7 +76,7 @@ docker-build:
|
||||
docker-push:
|
||||
./scripts/docker-build.sh --push
|
||||
|
||||
.PHONY: all install run build build-frontend build-debug dev \
|
||||
.PHONY: all install run build build-frontend pack-frontend build-debug dev \
|
||||
test test-coverage test-coverage-html test-frontend-e2e \
|
||||
update-i18n update-conf update-conf-docs \
|
||||
update-docs-features update-swagger \
|
||||
|
||||
@@ -40,6 +40,7 @@ export const en = defineConfig({
|
||||
{ text: 'LaTeX', link: '/en/guide/frontend/latex.md' },
|
||||
{ text: 'Image Lightbox', link: '/en/guide/frontend/lightbox.md' },
|
||||
{ text: 'Image Lazy Load', link: '/en/guide/frontend/img-lazy-load.md' },
|
||||
{ text: 'Voting', link: '/zh/guide/frontend/voting.md' },
|
||||
{ text: 'IP Region', link: '/en/guide/frontend/ip-region.md' },
|
||||
{ text: 'Localization', link: '/en/guide/frontend/i18n.md' },
|
||||
{ text: 'Development Documentation', link: '/en/develop/index.md' },
|
||||
@@ -82,7 +83,8 @@ export const en = defineConfig({
|
||||
{ text: 'Import to Blog', link: '/en/develop/import-blog.md' },
|
||||
{ text: 'Import to Framework', link: '/en/develop/import-framework.md' },
|
||||
{ text: 'Frontend API', link: '/en/develop/fe-api.md' },
|
||||
{ text: 'Frontend Event', link: '/en/develop/event.md' },
|
||||
{ text: 'Frontend Events', link: '/en/develop/event.md' },
|
||||
{ text: 'Frontend Types', link: 'https://artalk.js.org/typedoc/' },
|
||||
{ text: 'Plugin Development', link: '/en/develop/plugin.md' },
|
||||
{ text: 'Compatibility', link: '/en/develop/compatibility.md' },
|
||||
{
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { defineConfig } from 'vitepress'
|
||||
import {
|
||||
GitChangelog,
|
||||
GitChangelogMarkdownSection,
|
||||
} from '@nolebase/vitepress-plugin-git-changelog/vite'
|
||||
import * as Version from '../../code/ArtalkVersion.json'
|
||||
|
||||
export const shared = defineConfig({
|
||||
@@ -67,5 +71,28 @@ export const shared = defineConfig({
|
||||
server: {
|
||||
open: '/zh/guide/intro.html',
|
||||
},
|
||||
plugins: [
|
||||
GitChangelog({
|
||||
repoURL: () => 'https://github.com/ArtalkJS/Artalk',
|
||||
mapAuthors: [
|
||||
{
|
||||
name: 'qwqcode',
|
||||
username: 'qwqcode',
|
||||
mapByEmailAliases: ['qwqcode@gmail.com', '22412567+qwqcode@users.noreply.github.com'],
|
||||
},
|
||||
{
|
||||
name: 'pluveto',
|
||||
username: 'pluveto',
|
||||
mapByEmailAliases: ['i@pluvet.com', '50045289+pluveto@users.noreply.github.com'],
|
||||
},
|
||||
{
|
||||
name: 'Mr.Hope',
|
||||
username: 'Mister-Hope',
|
||||
mapByEmailAliases: ['mister-hope@outlook.com'],
|
||||
},
|
||||
],
|
||||
}),
|
||||
GitChangelogMarkdownSection(),
|
||||
],
|
||||
},
|
||||
})
|
||||
@@ -40,6 +40,7 @@ export const zh = defineConfig({
|
||||
{ text: 'LaTeX', link: '/zh/guide/frontend/latex.md' },
|
||||
{ text: '图片灯箱', link: '/zh/guide/frontend/lightbox.md' },
|
||||
{ text: '图片懒加载', link: '/zh/guide/frontend/img-lazy-load.md' },
|
||||
{ text: '投票功能', link: '/zh/guide/frontend/voting.md' },
|
||||
{ text: 'IP 属地', link: '/zh/guide/frontend/ip-region.md' },
|
||||
{ text: '多语言', link: '/zh/guide/frontend/i18n.md' },
|
||||
{ text: '开发文档', link: '/zh/develop/index.md' },
|
||||
@@ -85,7 +86,8 @@ export const zh = defineConfig({
|
||||
{ text: '置入博客', link: '/zh/develop/import-blog.md' },
|
||||
{ text: '置入框架', link: '/zh/develop/import-framework.md' },
|
||||
{ text: '前端 API', link: '/zh/develop/fe-api.md' },
|
||||
{ text: '前端 Event', link: '/zh/develop/event.md' },
|
||||
{ text: '前端 Events', link: '/zh/develop/event.md' },
|
||||
{ text: '前端 Types', link: 'https://artalk.js.org/typedoc/' },
|
||||
{ text: '插件开发', link: '/zh/develop/plugin.md' },
|
||||
{ text: '兼容性', link: '/zh/develop/compatibility.md' },
|
||||
{
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
import { Theme } from 'vitepress'
|
||||
import { NolebaseGitChangelogPlugin } from '@nolebase/vitepress-plugin-git-changelog/client'
|
||||
import Layout from './Layout.vue'
|
||||
import Artalk from './Artalk.vue'
|
||||
import Artransfer from './Artransfer.vue'
|
||||
|
||||
import '@nolebase/vitepress-plugin-git-changelog/client/style.css'
|
||||
|
||||
export default {
|
||||
...DefaultTheme,
|
||||
|
||||
@@ -12,6 +15,7 @@ export default {
|
||||
enhanceApp({ app, router, siteData }) {
|
||||
app.component('Artransfer', Artransfer)
|
||||
app.component('Artalk', Artalk)
|
||||
app.use(NolebaseGitChangelogPlugin)
|
||||
|
||||
// app is the Vue 3 app instance from `createApp()`.
|
||||
// router is VitePress' custom router. `siteData` is
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Frontend API
|
||||
|
||||
Here are some of the main Artalk client APIs for operating the Artalk instance in frontend code.
|
||||
|
||||
For more detailed API information, you can browse the [TypeDoc documentation](https://artalk.js.org/typedoc/).
|
||||
|
||||
## Install Dependencies
|
||||
|
||||
Use your preferred package manager to install Artalk:
|
||||
|
||||
@@ -98,32 +98,37 @@ onBeforeUnmount(() => {
|
||||
::: code-group
|
||||
|
||||
```tsx [React Hooks]
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { useCallback, useRef } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import 'artalk/Artalk.css'
|
||||
import Artalk from 'artalk'
|
||||
|
||||
const ArtalkComment = () => {
|
||||
const container = useRef<HTMLDivElement>(null)
|
||||
const location = useLocation()
|
||||
const { pathname } = useLocation()
|
||||
const artalk = useRef<Artalk>()
|
||||
|
||||
useEffect(() => {
|
||||
artalk.current = Artalk.init({
|
||||
el: container.current!,
|
||||
pageKey: location.pathname,
|
||||
pageTitle: document.title,
|
||||
server: 'http://localhost:8080',
|
||||
site: 'Artalk Blog',
|
||||
// ...
|
||||
})
|
||||
const handleContainerInit = useCallback(
|
||||
(node: HTMLDivElement | null) => {
|
||||
if (!node) {
|
||||
return
|
||||
}
|
||||
if (artalk.current) {
|
||||
artalk.current.destroy()
|
||||
artalk.current = undefined
|
||||
}
|
||||
artalk.current = Artalk.init({
|
||||
el: node,
|
||||
pageKey: pathname,
|
||||
pageTitle: document.title,
|
||||
server: 'http://localhost:8080',
|
||||
site: 'Artalk Blog',
|
||||
// ...
|
||||
})
|
||||
},
|
||||
[pathname],
|
||||
)
|
||||
|
||||
return () => {
|
||||
artalk.current?.destroy()
|
||||
}
|
||||
}, [location.pathname])
|
||||
|
||||
return <div ref={container}></div>
|
||||
return <div ref={handleContainerInit}></div>
|
||||
}
|
||||
|
||||
export default ArtalkComment
|
||||
|
||||
@@ -12,8 +12,9 @@ Thank you for your interest and support of Artalk. We deeply understand that the
|
||||
|
||||
- [Embed in Blog](./import-blog.md)
|
||||
- [Embed in Framework](./import-framework.md)
|
||||
- [Frontend API Documentation](./fe-api.md)
|
||||
- [Frontend API](./fe-api.md)
|
||||
- [Frontend Events](./event.md)
|
||||
- [Frontend Types](https://artalk.js.org/typedoc/)
|
||||
- [Frontend Configuration](../guide/frontend/config.md)
|
||||
|
||||
**Plugin Development**
|
||||
|
||||
@@ -276,7 +276,7 @@ ATK_TRUSTED_DOMAINS_0="https://a.com"
|
||||
| **ATK_FRONTEND_UABADGE** | `false` | User UA badge | frontend.uaBadge (UI Settings > User UA badge) |
|
||||
| **ATK_FRONTEND_VERSIONCHECK** | `true` | Version check | frontend.versionCheck (UI Settings > Version check) |
|
||||
| **ATK_FRONTEND_VOTE** | `true` | Vote button | frontend.vote (UI Settings > Vote button) |
|
||||
| **ATK_FRONTEND_VOTEDOWN** | `false` | Dislike button | frontend.voteDown (UI Settings > Dislike button) |
|
||||
| **ATK_FRONTEND_VOTEDOWN** | `false` | Vote down button | frontend.voteDown (UI Settings > Vote down button) |
|
||||
|
||||
|
||||
## Web server
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
# UI Configuration
|
||||
|
||||
UI configuration also known as Client (Front-end) configuration, is the configuration of the Artalk interface.
|
||||
UI configuration also known as Client (Frontend) configuration, is the configuration of the Artalk interface.
|
||||
|
||||
You can modify the UI configuration in the "[Dashboard](./sidebar.md#dashboard)" without changing the front-end code. Additionally, [Configuration files](../backend/config.md#frontend-configuration-frontend) and [Environment variables](../env.md#ui-settings) are also supported.
|
||||
You can modify the UI configuration in the "[Dashboard](./sidebar.md#dashboard)" without changing the frontend code. Additionally, [Configuration files](../backend/config.md#frontend-configuration-frontend) and [Environment variables](../env.md#ui-settings) are also supported.
|
||||
|
||||
The priority of UI configuration is as follows:
|
||||
|
||||
```
|
||||
Environment Variables > Configuration File = Dashboard > Front-end Code
|
||||
Frontend Code > Environment Variables > Dashboard = Configuration File
|
||||
```
|
||||
|
||||
If you have modified the UI configuration through environment variables, configuration files, or the Dashboard, but the changes are not taking effect, please consider the issue of UI configuration priority.
|
||||
|
||||
## Configuring the Interface via `Artalk.init`
|
||||
|
||||
When initializing with `Artalk.init` on the front-end page, pass parameters to configure the interface. Below are all the available configuration options.
|
||||
When initializing with `Artalk.init` on the frontend page, pass parameters to configure the interface. Below are all the available configuration options.
|
||||
|
||||
```js
|
||||
const artalk = Artalk.init({
|
||||
@@ -83,14 +85,20 @@ Deploy the backend and ensure that the backend address is accessible from the fr
|
||||
|
||||
Artalk supports unified management of multiple sites, this item is used for site isolation.
|
||||
|
||||
### useBackendConf
|
||||
### preferRemoteConf
|
||||
|
||||
**Refer to Backend Configuration**
|
||||
**Prefer Remote Configuration**
|
||||
|
||||
- Type: `Boolean`
|
||||
- Default: `true` (enabled by default)
|
||||
- Default: `false` (disabled by default)
|
||||
|
||||
You can define the front-end configuration in the backend configuration file, so that the front-end configuration always refers to the backend.
|
||||
This config option is disabled by default, meaning Artalk will prioritize using local config. Local config refers to the settings passed through `Artalk.init` in the frontend code.
|
||||
|
||||
When enabled, Artalk will prioritize using remote config. If the remote config does not exist, it will resort to using the local config. Remote config refers to the settings returned by the backend server program, allowing for dynamic config of the Artalk client. You can modify the user interface config in the "[Dashboard](./sidebar.md#dashboard)".
|
||||
|
||||
::: warning Update Notice
|
||||
As of version v2.10.0, the `useBackendConf` configuration option has been deprecated and is always `true`. Please use `preferRemoteConf` instead.
|
||||
:::
|
||||
|
||||
## Internationalization (i18n)
|
||||
|
||||
@@ -280,6 +288,17 @@ When querying comment counts and page views, Artalk's statistical component uses
|
||||
|
||||
To facilitate theme adaptation, you can customize the attribute name as needed, for example, replacing it with `data-path`, the HTML tag would be `<span data-path="/t/1.html"></span>`.
|
||||
|
||||
### pageVote
|
||||
|
||||
**Page Voting Buttons**
|
||||
|
||||
- Type: `Boolean | { upBtnEl: string; downBtnEl: string; upCountEl: string; downCountEl: string; activeClass: string }`
|
||||
- Default: `true`
|
||||
|
||||
Enable the page voting feature (upvote / downvote).
|
||||
|
||||
For details, see: [Page Voting](./voting.md#page-voting)
|
||||
|
||||
### vote
|
||||
|
||||
**Voting Buttons**
|
||||
@@ -519,4 +538,4 @@ Allow height limit area to scroll.
|
||||
- Type: `Boolean`
|
||||
- Default: `true`
|
||||
|
||||
Display a warning prompt when the front-end and back-end versions are incompatible.
|
||||
Display a warning prompt when the frontend and backend versions are incompatible.
|
||||
@@ -0,0 +1,123 @@
|
||||
# Voting Feature
|
||||
|
||||
Artalk supports voting on comments and pages, allowing users to click the "Up" or "Down" buttons to cast their votes. The comment list can be sorted based on the number of votes, which helps users better assess the quality of the comments.
|
||||
|
||||
## Comment Voting
|
||||
|
||||
The comment voting feature is enabled by default, allowing users to vote on comments.
|
||||
|
||||
You can find the "UI Settings" in the Dashboard to modify the "Vote Button" option to enable or disable comment voting.
|
||||
|
||||
Environment variable for the vote button:
|
||||
|
||||
```
|
||||
ATK_FRONTEND_VOTE=1
|
||||
```
|
||||
|
||||
Configuration file for the vote button:
|
||||
|
||||
```yaml
|
||||
frontend:
|
||||
vote: true
|
||||
```
|
||||
|
||||
### Down Button
|
||||
|
||||
By default, Artalk does not display the Down button. You can find the "UI Settings" in the Dashboard to modify the "Vote Down Button" option to enable it.
|
||||
|
||||
Environment variable for the Down button:
|
||||
|
||||
```
|
||||
ATK_FRONTEND_VOTE_DOWN=1
|
||||
```
|
||||
|
||||
Configuration file for the Down button:
|
||||
|
||||
```yaml
|
||||
frontend:
|
||||
voteDown: true
|
||||
```
|
||||
|
||||
## Page Voting
|
||||
|
||||
Artalk supports voting on pages. To enable page voting, you need to add elements in the page to display the voting buttons, which Artalk will automatically initialize on load:
|
||||
|
||||
```html
|
||||
<div>
|
||||
<span class="artalk-page-vote-up"></span>
|
||||
<span class="artalk-page-vote-down"></span>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Voted Status Styling
|
||||
|
||||
When users click the page voting button, the element will be given an `active` class to indicate that the user has voted. For example:
|
||||
|
||||
```html
|
||||
<span class="artalk-page-vote-up active"></span>
|
||||
```
|
||||
|
||||
You can customize the voted button style using CSS:
|
||||
|
||||
```css
|
||||
.artalk-page-vote-up.active {
|
||||
color: #0083ff;
|
||||
}
|
||||
```
|
||||
|
||||
The default added class name is `active`, but it can be changed using `pageVote.activeClass` in the client configuration:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
pageVote: {
|
||||
activeClass: 'active',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Custom Element Selectors
|
||||
|
||||
By default, Artalk searches for `.artalk-page-vote-up` and `.artalk-page-vote-down` as the voting button elements.
|
||||
|
||||
You can customize the voting button selectors by modifying the `pageVote.upBtnEl` and `pageVote.downBtnEl` configuration in the client:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
pageVote: {
|
||||
upBtnEl: '.artalk-page-vote-up',
|
||||
downBtnEl: '.artalk-page-vote-down',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Further Customizing Page Voting Buttons
|
||||
|
||||
If the voting buttons do not contain any child elements, Artalk will output the text "Up (n)" into the element.
|
||||
|
||||
If you want to output the vote count into a separate element, you can add a tag inside the button, for example:
|
||||
|
||||
```html
|
||||
<div class="artalk-page-vote">
|
||||
<span class="artalk-page-vote-up">
|
||||
👍 (<span class="artalk-page-vote-up-count"></span>)
|
||||
</span>
|
||||
<span class="artalk-page-vote-down">
|
||||
👎 (<span class="artalk-page-vote-down-count"></span>)
|
||||
</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
To further customize, you can replace the text with icons or add other styles.
|
||||
|
||||
The default selectors for vote counts are `.artalk-page-vote-up-count` and `.artalk-page-vote-down-count`.
|
||||
|
||||
You can modify `pageVote.upCountEl` and `pageVote.downCountEl` to customize the vote count output elements:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
pageVote: {
|
||||
upCountEl: '.artalk-page-vote-up-count',
|
||||
downCountEl: '.artalk-page-vote-down-count',
|
||||
},
|
||||
})
|
||||
```
|
||||
@@ -15,12 +15,13 @@
|
||||
"serve:docs": "vitepress serve"
|
||||
},
|
||||
"devDependencies": {
|
||||
"artalk": "workspace:^",
|
||||
"vitepress": "1.3.4",
|
||||
"@artalk/plugin-katex": "^0.2.4",
|
||||
"@artalk/plugin-lightbox": "^0.2.4",
|
||||
"@nolebase/vitepress-plugin-git-changelog": "^2.6.1",
|
||||
"artalk": "workspace:^",
|
||||
"katex": "^0.16.11",
|
||||
"lightgallery": "^2.7.2",
|
||||
"vue": "^3.5.6"
|
||||
"vitepress": "1.4.1",
|
||||
"vue": "^3.5.12"
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,10 @@
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"types": ["vite/client"],
|
||||
"resolveJsonModule": true
|
||||
"resolveJsonModule": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler"
|
||||
},
|
||||
"include": ["**/.vitepress/**/*"]
|
||||
"include": ["**/.vitepress/**/*.ts", "**/.vitepress/**/*.mts", "**/.vitepress/**/*.vue"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -183,6 +183,30 @@ pnpm check:publish
|
||||
|
||||
这将启动检查存储库中所有公共包的过程,以确保它们在 npm 上是最新的。
|
||||
|
||||
### 开发和调试 `@artalk/plugin-kit` 和 `eslint-plugin-artalk`
|
||||
|
||||
这个 monorepo 默认会从 NPM 安装最新版本的 `@artalk/plugin-kit` 和 `eslint-plugin-artalk` 发布包。要开发和调试这些包,请在仓库的根目录下运行以下命令:
|
||||
|
||||
```sh
|
||||
pnpm link --global --dir ui/eslint-plugin-artalk
|
||||
pnpm link --global --dir ui/plugin-kit
|
||||
|
||||
pnpm link eslint-plugin-artalk
|
||||
pnpm link @artalk/plugin-kit
|
||||
```
|
||||
|
||||
执行这些命令后,`eslint-plugin-artalk` 和 `@artalk/plugin-kit` 包将链接到本地开发环境中的 monorepo 工作空间。你可以在 `ui/eslint-plugin-artalk` 和 `ui/plugin-kit` 目录中修改源代码,这些更改会自动反映在 monorepo 工作空间中。如果你希望取消链接这些包,请运行以下命令:
|
||||
|
||||
```sh
|
||||
pnpm unlink eslint-plugin-artalk
|
||||
pnpm unlink @artalk/plugin-kit
|
||||
|
||||
pnpm uninstall --global eslint-plugin-artalk
|
||||
pnpm uninstall --global @artalk/plugin-kit
|
||||
```
|
||||
|
||||
更多详情请参考 [pnpm link 文档](https://pnpm.io/cli/link)。
|
||||
|
||||
## Docker 开发
|
||||
|
||||
### 构建 Docker 镜像
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Frontend API
|
||||
|
||||
以下列出了一些主要的 Artalk 客户端 API,用于在前端代码中操作 Artalk 实例。
|
||||
|
||||
更多详细 API 信息可以通过 [TypeDoc 文档](https://artalk.js.org/typedoc/) 浏览。
|
||||
|
||||
## 安装依赖
|
||||
|
||||
用你喜欢的包管理器安装 Artalk:
|
||||
|
||||
@@ -98,32 +98,37 @@ onBeforeUnmount(() => {
|
||||
::: code-group
|
||||
|
||||
```tsx [React Hooks]
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { useCallback, useRef } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import 'artalk/Artalk.css'
|
||||
import Artalk from 'artalk'
|
||||
|
||||
const ArtalkComment = () => {
|
||||
const container = useRef<HTMLDivElement>(null)
|
||||
const location = useLocation()
|
||||
const { pathname } = useLocation()
|
||||
const artalk = useRef<Artalk>()
|
||||
|
||||
useEffect(() => {
|
||||
artalk.current = Artalk.init({
|
||||
el: container.current!,
|
||||
pageKey: location.pathname,
|
||||
pageTitle: document.title,
|
||||
server: 'http://localhost:8080',
|
||||
site: 'Artalk 的博客',
|
||||
// ...
|
||||
})
|
||||
const handleContainerInit = useCallback(
|
||||
(node: HTMLDivElement | null) => {
|
||||
if (!node) {
|
||||
return
|
||||
}
|
||||
if (artalk.current) {
|
||||
artalk.current.destroy()
|
||||
artalk.current = undefined
|
||||
}
|
||||
artalk.current = Artalk.init({
|
||||
el: node,
|
||||
pageKey: pathname,
|
||||
pageTitle: document.title,
|
||||
server: 'http://localhost:8080',
|
||||
site: 'Artalk 的博客',
|
||||
// ...
|
||||
})
|
||||
},
|
||||
[pathname],
|
||||
)
|
||||
|
||||
return () => {
|
||||
artalk.current?.destroy()
|
||||
}
|
||||
}, [container, location.pathname])
|
||||
|
||||
return <div ref={container}></div>
|
||||
return <div ref={handleContainerInit}></div>
|
||||
}
|
||||
|
||||
export default ArtalkComment
|
||||
|
||||
@@ -12,8 +12,9 @@
|
||||
|
||||
- [置入博客](./import-blog.md)
|
||||
- [置入框架](./import-framework.md)
|
||||
- [前端 API 文档](./fe-api.md)
|
||||
- [前端 Event](./event.md)
|
||||
- [前端 API](./fe-api.md)
|
||||
- [前端 Events](./event.md)
|
||||
- [前端 Types](https://artalk.js.org/typedoc/)
|
||||
- [前端配置](../guide/frontend/config.md)
|
||||
|
||||
**插件开发**
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
界面配置的优先级如下:
|
||||
|
||||
```
|
||||
环境变量 > 配置文件 = 控制中心 > 前端代码
|
||||
前端代码 > 环境变量 > 控制中心 = 配置文件
|
||||
```
|
||||
|
||||
如果你通过环境变量、配置文件或是在控制中心修改了界面配置,遇到了配置未生效的情况,请考虑配置优先级的问题。
|
||||
|
||||
## 通过 `Artalk.init` 配置界面
|
||||
|
||||
在前端页面调用 `Artalk.init` 初始化时传入参数来配置界面,本文列出了所有可用的配置项。
|
||||
@@ -81,14 +83,20 @@ artalk.update({ ... })
|
||||
|
||||
Artalk 支持多站点统一管理,此项用于站点隔离。
|
||||
|
||||
### useBackendConf
|
||||
### preferRemoteConf
|
||||
|
||||
**引用后端的配置**
|
||||
**优先使用远程配置**
|
||||
|
||||
- 类型:`Boolean`
|
||||
- 默认值:`true`(默认启用)
|
||||
- 默认值:`false`(默认关闭)
|
||||
|
||||
可以在后端的配置文件中定义前端的配置,让前端配置始终引用后端。
|
||||
默认情况下,该配置项为关闭状态,Artalk 界面配置优先级为:优先使用本地的配置。本地配置是指在前端代码中通过 `Artalk.init` 传入的配置。
|
||||
|
||||
开启后,Artalk 会优先使用远程配置,如果远程配置不存在则使用本地配置。远程配置是指后端服务器程序返回的配置,用于动态配置 Artalk 客户端。你可以在「[控制中心](./sidebar.md#控制中心)」中修改用户界面的配置。
|
||||
|
||||
::: warning 更新注意
|
||||
从 v2.10.0 开始,`useBackendConf` 配置项已废弃并始终为 `true`,请使用 `preferRemoteConf` 替代。
|
||||
:::
|
||||
|
||||
## 国际化 (i18n)
|
||||
|
||||
@@ -278,6 +286,17 @@ Artalk 统计组件查询评论数和浏览量时,会通过该属性名来查
|
||||
|
||||
为了便于主题适配,可根据需要自定义属性名,例如将其替换为 `data-path`,则 HTML 标签为 `<span data-path="/t/1.html"></span>`。
|
||||
|
||||
### pageVote
|
||||
|
||||
**页面投票**
|
||||
|
||||
- 类型:`Boolean | { upBtnEl: string; downBtnEl: string; upCountEl: string; downCountEl: string; activeClass: string }`
|
||||
- 默认值: `true`
|
||||
|
||||
启用页面投票功能,用户可以为页面投票。
|
||||
|
||||
详情参考:[页面投票](./voting.md#页面投票)
|
||||
|
||||
### vote
|
||||
|
||||
**投票按钮**
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
# 投票功能
|
||||
|
||||
Artalk 支持对评论和页面进行投票,用户可以点击“赞同”或“反对”按钮进行投票。评论列表支持通过投票数进行排序。评论投票功能可以帮助用户更好地了解评论的质量。
|
||||
|
||||
## 评论投票
|
||||
|
||||
评论投票功能默认启用,用户可以对评论进行投票。
|
||||
|
||||
你可以在控制台设置界面找到「界面配置」,修改「投票按钮」选项来启用或禁用评论投票功能。
|
||||
|
||||
投票按钮的环境变量:
|
||||
|
||||
```
|
||||
ATK_FRONTEND_VOTE=1
|
||||
```
|
||||
|
||||
投票按钮的配置文件:
|
||||
|
||||
```yaml
|
||||
frontend:
|
||||
vote: true
|
||||
```
|
||||
|
||||
### 反对按钮
|
||||
|
||||
默认情况下,Artalk 不会显示反对按钮。你可以在控制台设置界面找到「界面配置」,修改「反对按钮」选项来启用反对按钮。
|
||||
|
||||
反对按钮的环境变量:
|
||||
|
||||
```
|
||||
ATK_FRONTEND_VOTE_DOWN=1
|
||||
```
|
||||
|
||||
反对按钮的配置文件:
|
||||
|
||||
```yaml
|
||||
frontend:
|
||||
voteDown: true
|
||||
```
|
||||
|
||||
## 页面投票
|
||||
|
||||
Artalk 支持对页面进行投票,你需要在页面中添加元素来显示页面投票按钮,Artalk 在加载时会自动初始化页面投票按钮:
|
||||
|
||||
```html
|
||||
<div>
|
||||
<span class="artalk-page-vote-up"></span>
|
||||
<span class="artalk-page-vote-down"></span>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 已投票状态样式
|
||||
|
||||
当用户点击了页面投票按钮后,元素会被添加 `active` 类名,表示用户已经投票,例如:
|
||||
|
||||
```html
|
||||
<span class="artalk-page-vote-up active"></span>
|
||||
```
|
||||
|
||||
你可以通过 CSS 样式来自定义按钮的已投票样式:
|
||||
|
||||
```css
|
||||
.artalk-page-vote-up.active {
|
||||
color: #0083ff;
|
||||
}
|
||||
```
|
||||
|
||||
默认添加的类名为 `active`,可以在客户端通过 `pageVote.activeClass` 来修改:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
pageVote: {
|
||||
activeClass: 'active',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### 自定义元素选择器
|
||||
|
||||
Artalk 默认查找 `.artalk-page-vote-up` 和 `.artalk-page-vote-down` 作为投票按钮元素。
|
||||
|
||||
修改客户端的 `pageVote.upBtnEl` 和 `pageVote.downBtnEl` 配置可以自定义投票按钮选择器:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
pageVote: {
|
||||
upBtnEl: '.artalk-page-vote-up',
|
||||
downBtnEl: '.artalk-page-vote-down',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### 进一步自定义页面投票按钮
|
||||
|
||||
如果投票按钮内没有子元素,Artalk 会输出文字 "赞同 (n)" 到元素中。
|
||||
|
||||
如果你想输出投票数量到单独的元素,可以在按钮中添加一个标签,例如:
|
||||
|
||||
```html
|
||||
<div class="artalk-page-vote">
|
||||
<span class="artalk-page-vote-up">
|
||||
👍 (<span class="artalk-page-vote-up-count"></span>)
|
||||
</span>
|
||||
<span class="artalk-page-vote-down">
|
||||
👎 (<span class="artalk-page-vote-down-count"></span>)
|
||||
</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
更进一步,你可以将文字修改为图标,或者添加其他样式。
|
||||
|
||||
投票数选择器默认为 `.artalk-page-vote-up-count` 和 `.artalk-page-vote-down-count`,
|
||||
|
||||
可以修改 `pageVote.upCountEl` 和 `pageVote.downCountEl` 自定义投票数输出元素:
|
||||
|
||||
```js
|
||||
Artalk.init({
|
||||
pageVote: {
|
||||
upCountEl: '.artalk-page-vote-up-count',
|
||||
downCountEl: '.artalk-page-vote-down-count',
|
||||
},
|
||||
})
|
||||
```
|
||||
@@ -15,18 +15,19 @@
|
||||
"update:readme": "npx tsx ./scripts/update-readme.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@headlessui/react": "^2.1.10",
|
||||
"@types/react": "^18.3.11",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react-swc": "^3.7.1",
|
||||
"i18next": "^23.16.0",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"include-media": "^2.0.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^15.0.3",
|
||||
"react-icons": "5.3.0",
|
||||
"@headlessui/react": "^2.1.8",
|
||||
"@types/react": "^18.3.7",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vitejs/plugin-react-swc": "^3.7.0",
|
||||
"i18next": "^23.15.1",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"react-i18next": "^15.0.2",
|
||||
"usehooks-ts": "^3.1.0"
|
||||
"usehooks-ts": "^3.1.0",
|
||||
"vanilla-lazyload": "^19.1.3"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 45 KiB |
@@ -9,14 +9,19 @@
|
||||
--color-bg: #fff;
|
||||
--color-bg-transparent: rgba(255, 255, 255, 0.9);
|
||||
--color-bg-grey: #f6f8fa;
|
||||
--color-bg-grey-deep: #f2f2f5;
|
||||
--color-bg-blue: #e5effe;
|
||||
--color-border: #e1e4ec;
|
||||
--color-border-light: #e4e4e4;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
background-color: var(--color-bg);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
padding-top: 70px;
|
||||
}
|
||||
|
||||
:host,
|
||||
@@ -74,3 +79,7 @@ a {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.app-grey-bg {
|
||||
background: var(--color-bg-grey-deep);
|
||||
}
|
||||
@@ -8,11 +8,16 @@ import { useDarkMode } from './hooks/darkmode'
|
||||
import { Header } from './components/Header'
|
||||
import { Slogan } from './components/Slogan'
|
||||
import { Footer } from './components/Footer'
|
||||
import { SlightFeature } from './components/Features/SlightFeature'
|
||||
import { QuickFeature } from './components/Features/QuickFeature'
|
||||
import { FullFeature } from './components/Features/FullFeature'
|
||||
import { FuncFeature } from './components/Features/FuncFeature'
|
||||
import { SafeFeature } from './components/Features/SafeFeature'
|
||||
import { SlightFeature } from './components/Features/Slight'
|
||||
import { QuickFeature } from './components/Features/Quick'
|
||||
import { FullFeature } from './components/Features/Full'
|
||||
import { FuncsFeature } from './components/Features/Funcs'
|
||||
import { SafeFeature } from './components/Features/Safe'
|
||||
import { Plugins } from './components/Features/Plugins'
|
||||
import { Group } from './components/FeatureComps/Group'
|
||||
import { StartFeature } from './components/Features/Start'
|
||||
import { FrameworksFeature } from './components/Features/Frameworks'
|
||||
import { GreyCardsFeature } from './components/Features/GreyCards'
|
||||
|
||||
function App() {
|
||||
const { t, i18n } = useTranslation()
|
||||
@@ -33,11 +38,23 @@ function App() {
|
||||
<Header darkModeHandler={{ isDarkMode, toggle: toggleDarkMode }} />
|
||||
<Slogan />
|
||||
|
||||
<SlightFeature />
|
||||
<QuickFeature />
|
||||
<StartFeature />
|
||||
<GreyCardsFeature />
|
||||
|
||||
<FrameworksFeature />
|
||||
|
||||
<Group>
|
||||
<SlightFeature />
|
||||
<QuickFeature />
|
||||
</Group>
|
||||
<Group>
|
||||
<SafeFeature />
|
||||
</Group>
|
||||
<Group>
|
||||
<Plugins />
|
||||
</Group>
|
||||
<FullFeature />
|
||||
<FuncFeature />
|
||||
<SafeFeature />
|
||||
<FuncsFeature />
|
||||
|
||||
<Footer />
|
||||
</>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path d="M113.744 41.999a18.558 18.558 0 0 0-.8-.772c-.272-.246-.528-.524-.8-.771s-.528-.525-.8-.771c-.272-.247-.528-.525-.8-.772s-.528-.524-.8-.771-.528-.525-.8-.772-.528-.524-.8-.771c7.936 7.52 12.483 17.752 12.656 28.481 0 25.565-26.912 46.363-60 46.363-18.528 0-35.104-6.526-46.128-16.756l.8.772.8.771.8.772.8.771.8.772.8.771.8.771c11.008 10.662 27.952 17.527 46.928 17.527 33.088 0 60-20.797 60-46.285 0-10.893-4.864-21.215-13.456-29.33z"/><path fill="#fbf0df" d="M116.8 65.08c0 23.467-25.072 42.49-56 42.49s-56-19.023-56-42.49c0-14.55 9.6-27.401 24.352-35.023C43.904 22.435 53.088 14.628 60.8 14.628S75.104 21 92.448 30.058C107.2 37.677 116.8 50.53 116.8 65.08Z"/><path fill="#f6dece" d="M116.8 65.08a32.314 32.314 0 0 0-1.28-8.918c-4.368 51.377-69.36 53.846-94.912 38.48 11.486 8.584 25.66 13.144 40.192 12.928 30.88 0 56-19.054 56-42.49z"/><path fill="#fffefc" d="M39.248 27.234c7.152-4.135 16.656-11.896 26-11.911a15.372 15.372 0 0 0-4.448-.695c-3.872 0-8 1.93-13.2 4.83-1.808 1.018-3.68 2.144-5.664 3.317-3.728 2.222-8 4.736-12.8 7.251C13.904 37.972 4.8 51.071 4.8 65.08v1.836c9.696-33.033 27.312-35.547 34.448-39.682z"/><path fill="#ccbea7" d="M56.192 18.532A24.553 24.553 0 0 1 53.867 29.1a25.407 25.407 0 0 1-6.683 8.671c-.448.386-.096 1.127.48.91 5.392-2.02 12.672-8.068 9.6-20.272-.128-.695-1.072-.51-1.072.123zm3.632 0a24.474 24.474 0 0 1 3.646 10.12c.445 3.587.08 7.224-1.07 10.662-.192.54.496 1.003.88.556 3.504-4.32 6.56-12.899-2.592-22.156-.464-.4-1.184.216-.864.756zm4.416-.262a25.702 25.702 0 0 1 7.521 7.925A24.71 24.71 0 0 1 75.2 36.414c-.016.13.02.26.101.365a.543.543 0 0 0 .718.117.509.509 0 0 0 .221-.313c1.472-5.384.64-14.564-11.472-19.332-.64-.246-1.056.587-.528.957zM34.704 34.315a27.418 27.418 0 0 0 9.91-5.222 26.262 26.262 0 0 0 6.842-8.663c.288-.556 1.2-.34 1.056.277-2.768 12.343-12.032 14.92-17.792 14.58-.608.016-.592-.802-.016-.972z"/><path d="M60.8 111.443c-33.088 0-60-20.798-60-46.363 0-15.429 9.888-29.823 26.448-38.448 4.8-2.469 8.912-4.953 12.576-7.128 2.016-1.203 3.92-2.33 5.76-3.379C51.2 12.916 56 10.771 60.8 10.771c4.8 0 8.992 1.852 14.24 4.845 1.6.88 3.2 1.836 4.912 2.885 3.984 2.376 8.48 5.06 14.4 8.131 16.56 8.625 26.448 23.004 26.448 38.448 0 25.565-26.912 46.363-60 46.363zm0-96.814c-3.872 0-8 1.928-13.2 4.829-1.808 1.018-3.68 2.144-5.664 3.317-3.728 2.222-8 4.736-12.8 7.251C13.904 37.972 4.8 51.071 4.8 65.08c0 23.436 25.12 42.506 56 42.506s56-19.07 56-42.506c0-14.01-9.104-27.108-24.352-35.023-6.048-3.086-10.768-5.986-14.592-8.27-1.744-1.033-3.344-1.99-4.8-2.838-4.848-2.778-8.384-4.32-12.256-4.32z"/><path fill="#b71422" d="M72.08 76.343c-.719 2.839-2.355 5.383-4.672 7.267a11.07 11.07 0 0 1-6.4 2.9 11.13 11.13 0 0 1-6.608-2.9c-2.293-1.892-3.906-4.436-4.608-7.267a1.073 1.073 0 0 1 .05-.5 1.11 1.11 0 0 1 .272-.428 1.19 1.19 0 0 1 .958-.322h19.744a1.185 1.185 0 0 1 .947.33 1.073 1.073 0 0 1 .317.92z"/><path fill="#ff6164" d="M54.4 83.733a11.24 11.24 0 0 0 6.592 2.932 11.239 11.239 0 0 0 6.576-2.932 16.652 16.652 0 0 0 1.6-1.65 10.904 10.904 0 0 0-3.538-2.564 11.26 11.26 0 0 0-4.302-1 10.121 10.121 0 0 0-4.549 1.192 9.71 9.71 0 0 0-3.451 3.097c.368.323.688.632 1.072.925z"/><path d="M54.656 82.514a8.518 8.518 0 0 1 2.97-2.347 8.836 8.836 0 0 1 3.734-.862 9.78 9.78 0 0 1 6.4 2.608c.368-.386.72-.787 1.056-1.188-2.035-1.87-4.726-2.933-7.536-2.978a10.487 10.487 0 0 0-4.335.975 10.125 10.125 0 0 0-3.489 2.666c.378.396.779.772 1.2 1.126z"/><path d="M60.944 87.436a12.078 12.078 0 0 1-7.12-3.086c-2.477-2.02-4.22-4.75-4.976-7.791-.054-.27-.045-.55.027-.817a1.83 1.83 0 0 1 .389-.726 2.25 2.25 0 0 1 .81-.595 2.32 2.32 0 0 1 .998-.192h19.744c.343-.007.683.06.996.196a2.3 2.3 0 0 1 .812.591c.182.212.313.46.382.728.07.267.076.545.018.815-.756 3.042-2.5 5.771-4.976 7.791a12.078 12.078 0 0 1-7.104 3.086zm-9.872-11.417c-.256 0-.32.108-.336.139.676 2.638 2.206 4.999 4.368 6.742a10.122 10.122 0 0 0 5.84 2.7 10.207 10.207 0 0 0 5.84-2.67c2.155-1.745 3.679-4.106 4.352-6.741a.333.333 0 0 0-.14-.113.348.348 0 0 0-.18-.026z"/><path fill="#febbd0" d="M85.152 77.3c5.17 0 9.36-2.377 9.36-5.308s-4.19-5.307-9.36-5.307c-5.17 0-9.36 2.376-9.36 5.307 0 2.931 4.19 5.307 9.36 5.307zm-48.432 0c5.17 0 9.36-2.377 9.36-5.308s-4.19-5.307-9.36-5.307c-5.17 0-9.36 2.376-9.36 5.307 0 2.931 4.19 5.307 9.36 5.307z"/><path d="M41.12 69.863a9.052 9.052 0 0 0 4.902-1.425 8.578 8.578 0 0 0 3.254-3.812 8.22 8.22 0 0 0 .508-4.913 8.41 8.41 0 0 0-2.408-4.357 8.92 8.92 0 0 0-4.514-2.33 9.12 9.12 0 0 0-5.096.48 8.755 8.755 0 0 0-3.96 3.131 8.287 8.287 0 0 0-1.486 4.725c0 2.252.927 4.412 2.577 6.005 1.65 1.594 3.888 2.492 6.223 2.496zm39.632 0a9.054 9.054 0 0 0 4.915-1.403 8.582 8.582 0 0 0 3.275-3.802 8.22 8.22 0 0 0 .528-4.917 8.408 8.408 0 0 0-2.398-4.368 8.92 8.92 0 0 0-4.512-2.344 9.12 9.12 0 0 0-5.103.473 8.756 8.756 0 0 0-3.967 3.13 8.287 8.287 0 0 0-1.49 4.73c-.004 2.245.914 4.4 2.555 5.994 1.64 1.593 3.869 2.495 6.197 2.507z"/><path fill="#fff" d="M38.4 61.902a3.4 3.4 0 0 0 1.844-Line truncated
|
||||
|
After Width: | Height: | Size: 5.4 KiB |
@@ -0,0 +1 @@
|
||||
<svg height="512" viewBox="0 0 5120 5120" width="512" xmlns="http://www.w3.org/2000/svg"><path d="m2560 0a2560 2560 0 1 1 0 5120 2560 2560 0 0 1 0-5120z"/><path d="m2460 1449c-744 0-1324 469-1324 1052 0 550 533 901 1359 884l25-1 91-3-23 60 3 6a668 668 0 0 1 18 47l2 6 3 10 4 14 3 9 4 10 3 11 4 16 5 17 3 11 5 18 5 19 4 19 5 20 4 14 5 22 5 22 7 30 3 16 5 24 5 25 6 26 7 37 6 30 8 42 4 21 7 33 6 34 8 46 9 48 8 50 9 51 9 52 9 54 9 56 7 43 11 73 5 30 12 77 9 63 8 48 9 66 5 33c549-73 1037-339 1393-728l11-12-51-190-135-505-84-314-74-276-46-168-29-106-17-64-16-56-6-24-4-13-2-7-2-6c-78-251-229-473-435-634-242-189-549-288-907-288zm-654 2669c-65-18-133 20-152 85l-1 3-112 416a2287 2287 0 0 0 215 93l17 7 121-451 1-3c16-66-23-133-89-150zm697-305c-66-18-134 20-153 85l-1 3-170 630v3a125 125 0 0 0 241 65l1-3 170-630v-3l3-14 1-5-4-21-6-29-4-18a125 125 0 0 0 -78-63zm-1185-649-8 19-1 4-170 630-1 3a125 125 0 0 0 241 66l1-3 154-572c-80-42-153-92-216-147zm-405-725c-66-17-134 21-153 85l-1 3-170 630v3a125 125 0 0 0 241 66l1-3 170-630v-3c16-66-23-133-88-151zm3811-143c-65-17-133 21-152 85l-1 3-170 630-1 3a125 125 0 0 0 242 66v-3l171-630v-4c16-65-23-132-89-150zm-4182-841a2284 2284 0 0 0 -267 838 124 124 0 0 0 62 38c65 17 133-21 152-85l1-3 170-630 1-3c16-66-23-133-89-151a127 127 0 0 0 -30-4zm3752 4c-66-17-133 21-153 85v3l-170 630-1 3a125 125 0 0 0 241 66l1-3 170-630 1-3c16-66-24-133-89-151z" fill="#fff"/><path d="m2620 1870a160 160 0 1 1 0 320 160 160 0 0 1 0-320z"/><path d="m1282 860c-65-17-133 21-152 85l-1 3-170 630-1 3a125 125 0 0 0 241 66l1-3 170-630 1-4c16-65-23-132-89-150zm2185 119c-66-17-134 21-153 85l-1 3-114 424a1399 1399 0 0 1 211 128l11 9 134-495v-3c16-66-23-133-88-151zm-1112-710a2299 2299 0 0 0 -238 34l-17 3-158 587-1 3a125 125 0 0 0 241 65l1-3 170-630 1-3a124 124 0 0 0 1-56zm1564 435-33 124-1 3a125 125 0 0 0 241 65l1-3 4-13a2312 2312 0 0 0 -197-165zm-989-414-60 223-1 3a125 125 0 0 0 241 65l1-3 63-235a2286 2286 0 0 0 -226-50z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1 @@
|
||||
<svg height="800" viewBox="0 0 24 24" width="800" xmlns="http://www.w3.org/2000/svg"><g fill="none"><path d="m21.4679537 3.20617761c.7135135 1.47335907-1.4548263 1.63088803-1.3436294 2.28880308.203861 1.18610039.0648649 2.94671815-.0926641 4.67953671-.2687258 2.9374517 1.9274132 9.9706563-2.650193 12.7969112-.8617761.5374517-2.9096525.8339768-4.9297297.8803089h-.0092665-.0092664c-2.0200772-.0463321-4.34594591-.3428572-5.20772197-.8803089-4.56833977-2.8262549-2.37220078-9.8594595-2.63166024-12.7969112-.16679536-1.73281856-.3057915-3.49343632-.09266409-4.67953671.11119691-.65791505-2.05714286-.8061776-1.34362934-2.28880308.61158301-1.26949807 2.12200772-.14826255 2.5019305-.54671815 1.93667954-2.01081081 4.29034749-2.54826255 6.14362934-2.5945946h1.019305c1.8440155.0926641 4.1976834.5930502 6.1436294 2.5945946.3799227.3984556 1.881081-.72277992 2.5019305.54671815zm-10.0355213 7.70038609c-.0833977.0370656-.2223938.9451738.203861.9915058.2872587.0277992 1.0749035.1204633 1.2602317 0 .36139-.2316602.3243243-.7876448.1204633-.9266409-.3428571-.2223939-1.4918919-.1111969-1.584556-.0648649zm-2.35366792-6.80154439c-.95444016-.11119692-2.55752896.77837837-2.7984556 2.66872586-.25945946 1.96447877 2.05714286 3.91042473 4.28108112 1.96447877 1.1953668-1.04710425 1.6123552-4.27181467-1.48262552-4.63320463zm6.44942082 0c-3.0949807.36138996-2.6779922 3.58610038-1.4826255 4.63320463 2.2239383 1.94594596 4.5405406 0 4.2810811-1.96447877-.2316602-1.89034749-1.834749-2.77992278-2.7984556-2.66872586z" fill="#8cc5e7"/><path d="m12.3127413 8.98841699c.5837838-.08339768 1.9830116.58378378 1.8903475 1.37142861-.1111969.9173745-3.6046332 1.0378378-3.7899614-.0555985-.1111969-.66718146.4169884-1.09343629 1.8996139-1.31583011zm7.8857143 7.38532821c-.2872587-.0092664-.4725869-.3891892-.4725869-.6208494 0-.4169885.0555985-.9081082.36139-1.1212356.6301158-.4355212 1.1305019 1.7513514.1111969 1.742085zm-15.78069498 0c-1.01930502.0092664-.51891892-2.1776062.11119691-1.742085.3057915.2131274.36138996.7042471.36138996 1.1212356 0 .2316602-.18532818.611583-.47258687.6208494zm13.84401548 6.7181467c.1853281.2687259.1945946.454054-.1019305.5930502-1.130502.5189189-2.1405406-.1760618-1.7606178-.3706564.8061776-.407722 1.3065637-1.0471042 1.8625483-.2223938zm-11.90733595.0926641c.55598456-.8247104 1.05637066-.1853282 1.86254827.2223938.37992278.1945946-.63011583.8895753-1.76061776.3706564-.2965251-.1389962-.28725869-.3243243-.10193051-.5930502z" fill="#b8937f"/><path d="m19.7351351 3.42857143c.0463321-.1945946.5281854-.28725869.7969112.04633204.3243244.3984556-.4725868.94517375-.5096525.72277993-.0926641-.6023166-.3428571-.57451738-.2872587-.76911197zm-14.85405402 0c.05559846.19459459-.19459459.16679537-.28725869.76911197-.03706563.22239382-.83397683-.32432433-.50965251-.72277993.25945946-.33359073.74131275-.24092664.7969112-.04633204zm10.86023162 4.51274131c-.5834174 0-1.0563706-.47295325-1.0563706-1.05637066 0-.5834174.4729532-1.05637065 1.0563706-1.05637065s1.0563707.47295325 1.0563707 1.05637065c0 .58341741-.4729533 1.05637066-1.0563707 1.05637066zm-.2779922-1.17683398c.184237 0 .3335907-.14935365.3335907-.33359073 0-.18423707-.1493537-.33359073-.3335907-.33359073-.1842371 0-.3335908.14935366-.3335908.33359073 0 .18423708.1493537.33359073.3335908.33359073zm-4.1050193 2.66872587c.1111969-.42625482.5003861-.56525096.815444-.57451737.8061776-.01853282 1.1119691.41698842 1.1397683.75984556.0555985.58378378-2.2054054.72277988-1.9552123-.18532819zm-2.48339773-1.49189189c-.5834174 0-1.05637065-.47295325-1.05637065-1.05637066 0-.5834174.47295325-1.05637065 1.05637065-1.05637065.58341741 0 1.05637066.47295325 1.05637066 1.05637065 0 .58341741-.47295325 1.05637066-1.05637066 1.05637066zm.27799228-1.17683398c.18423708 0 .33359074-.14935365.33359074-.33359073 0-.18423707-.14935366-.33359073-.33359074-.33359073-.18423707 0-.33359073.14935366-.33359073.33359073 0 .18423708.14935366.33359073.33359073.33359073z" fill="#000"/></g></svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1 @@
|
||||
<svg height="800" viewBox="0 0 512 512" width="800" xmlns="http://www.w3.org/2000/svg"><path d="m256.4 25.8-200 115.5-.4 230.2 199.6 114.7 200-115.5.4-230.2zm92.6 328.8-18.4 10.7-18.6-11v-79.3h-112v79.6l-18.4 10.7-18.6-11v-197l18.5-10.6 18.5 10.8v79.5h112v-79.6l18.5-10.6 18.5 10.8z" fill="#0e83cd"/></svg>
|
||||
|
After Width: | Height: | Size: 306 B |
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 370 391" xmlns="http://www.w3.org/2000/svg"><g clip-rule="evenodd" fill-rule="evenodd"><path d="m207.5 22.4 114.4 66.6c13.5 7.9 21.9 22.4 21.9 38v136.4c0 17.3-9.3 33.3-24.5 41.8l-113.5 63.9c-15.1 8.5-33.5 8.4-48.5-.2l-104.5-60.1c-16.4-9.5-26.6-27-26.6-45.9v-129.5c0-19.1 9.9-36.8 26.1-46.8l102.8-63.5c16-9.9 36.2-10.1 52.4-.7z" fill="#ff4088" stroke="#c9177e" stroke-width="27"/><path d="m105.6 298.2v-207.2h43.4v75.5h71.9v-75.5h43.5v207.2h-43.5v-90.6h-71.9v90.6z" fill="#fff"/></g></svg>
|
||||
|
After Width: | Height: | Size: 506 B |
@@ -0,0 +1 @@
|
||||
<svg height="292" viewBox="0 0 256 292" width="256" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" x1="68.188441%" x2="27.822694%" y1="17.486831%" y2="89.755142%"><stop offset="0" stop-color="#41873f"/><stop offset=".3288" stop-color="#418b3d"/><stop offset=".6352" stop-color="#419637"/><stop offset=".9319" stop-color="#3fa92d"/><stop offset="1" stop-color="#3fae2a"/></linearGradient><linearGradient id="b" x1="43.276547%" x2="159.245277%" y1="55.168777%" y2="-18.306138%"><stop offset=".1376" stop-color="#41873f"/><stop offset=".4032" stop-color="#54a044"/><stop offset=".7136" stop-color="#66b848"/><stop offset=".9081" stop-color="#6cc04a"/></linearGradient><linearGradient id="c" x1="-4.388804%" x2="101.499239%" y1="49.997207%" y2="49.997207%"><stop offset=".09191646" stop-color="#6cc04a"/><stop offset=".2864" stop-color="#66b848"/><stop offset=".5968" stop-color="#54a044"/><stop offset=".8624" stop-color="#41873f"/></linearGradient><mask id="d" fill="#fff"><path d="m134.922587 1.83244962c-4.343584-2.44326616-9.50159-2.44326616-13.845175 0l-114.29056154 65.96818658c-4.34358429 2.4432662-6.78685046 7.0583244-6.78685046 11.9448568v132.207847c0 4.886533 2.71474019 9.501591 6.78685046 11.944857l114.29056154 65.968188c4.343585 2.443264 9.501591 2.443264 13.845175 0l114.290561-65.968188c4.343585-2.443266 6.786852-7.058324 6.786852-11.944857v-132.207847c0-4.8865324-2.71474-9.5015906-6.786852-11.9448568z" fill="#fff"/></mask><mask id="e" fill="#fff"><path d="m134.922587 1.83244962c-4.343584-2.44326616-9.50159-2.44326616-13.845175 0l-114.29056154 65.96818658c-4.34358429 2.4432662-6.78685046 7.0583244-6.78685046 11.9448568v132.207847c0 4.886533 2.71474019 9.501591 6.78685046 11.944857l114.29056154 65.968188c4.343585 2.443264 9.501591 2.443264 13.845175 0l114.290561-65.968188c4.343585-2.443266 6.786852-7.058324 6.786852-11.944857v-132.207847c0-4.8865324-2.71474-9.5015906-6.786852-11.9448568z" fill="#fff"/></mask><path d="m134.922587 1.83244962c-4.343584-2.44326616-9.50159-2.44326616-13.845175 0l-114.29056154 65.96818658c-4.34358429 2.4432662-6.78685046 7.0583244-6.78685046 11.9448568v132.207847c0 4.886533 2.71474019 9.501591 6.78685046 11.944857l114.29056154 65.968188c4.343585 2.443264 9.501591 2.443264 13.845175 0l114.290561-65.968188c4.343585-2.443266 6.786852-7.058324 6.786852-11.944857v-132.207847c0-4.8865324-2.71474-9.5015906-6.786852-11.9448568z" fill="url(#a)"/><path d="m249.484623 67.8006362-114.83351-65.96818658c-1.085896-.54294803-2.443266-1.08589606-3.529162-1.35737008l-128.67868483 220.43690346c1.08589607 1.35737 2.44326618 2.443266 3.80063626 3.257688l114.83350957 65.968186c3.257689 1.90032 7.058325 2.443267 10.587487 1.35737l120.805939-220.9798506c-.814422-1.0858961-1.90032-1.9003183-2.986215-2.7147402z" fill="url(#b)" mask="url(#d)"/><path d="m249.756098 223.898195c3.257687-1.900317 5.700955-5.158004 6.786849-8.687166l-125.963944-215.00742421c-3.257688-.54294817-6.78685-.2714742-9.773065 1.62884406l-114.01908754 65.69671245 122.97773054 224.2375407c1.628844-.271475 3.529162-.814423 5.158006-1.628845z" fill="url(#c)" mask="url(#e)"/></svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg fill="none" viewBox="-11 -10.13 22 20.27" xmlns="http://www.w3.org/2000/svg"><circle fill="#087ea4" r="2"/><g stroke="#087ea4"><ellipse rx="10" ry="4.5"/><ellipse rx="10" ry="4.5" transform="matrix(.5 .8660254 -.8660254 .5 0 0)"/><ellipse rx="10" ry="4.5" transform="matrix(-.5 .8660254 -.8660254 -.5 0 0)"/></g></svg>
|
||||
|
After Width: | Height: | Size: 323 B |
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="18.918325" x2="119.14456" y1="3.92209" y2="52.626405"><stop offset=".1" stop-color="#76b3e1"/><stop offset=".3" stop-color="#dcf2fd"/><stop offset="1" stop-color="#76b3e1"/></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="73.901874" x2="56.35222" y1="27.750978" y2="86.196156"><stop offset="0" stop-color="#76b3e1"/><stop offset=".5" stop-color="#4377bb"/><stop offset="1" stop-color="#1f3b77"/></linearGradient><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="11.592552" x2="112.945829" y1="53.189926" y2="122.100494"><stop offset="0" stop-color="#315aa9"/><stop offset=".5" stop-color="#518ac8"/><stop offset="1" stop-color="#315aa9"/></linearGradient><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="57.318256" x2="16.422732" y1="61.481735" y2="211.458824"><stop offset="0" stop-color="#4377bb"/><stop offset=".5" stop-color="#1a336b"/><stop offset="1" stop-color="#1a336b"/></linearGradient><path d="m128 29.683s-42.667-31.396-75.673-24.151l-2.415.805c-4.83 1.61-8.855 4.025-11.27 7.245l-1.61 2.415-12.076 20.931 20.93 4.025c8.856 5.636 20.127 8.05 30.592 5.636l37.031 7.245z" fill="#76b3e1"/><path d="m128 29.683s-42.667-31.396-75.673-24.151l-2.415.805c-4.83 1.61-8.855 4.025-11.27 7.245l-1.61 2.415-12.076 20.931 20.93 4.025c8.856 5.636 20.127 8.05 30.592 5.636l37.031 7.245z" fill="url(#a)" opacity=".3"/><path d="m38.642 29.683-3.22.805c-13.687 4.025-17.712 16.906-10.467 28.176 8.05 10.465 24.956 16.1 38.641 12.076l49.912-16.906s-42.665-31.396-74.866-24.151z" fill="#518ac8"/><path d="m38.642 29.683-3.22.805c-13.687 4.025-17.712 16.906-10.467 28.176 8.05 10.465 24.956 16.1 38.641 12.076l49.912-16.906s-42.665-31.396-74.866-24.151z" fill="url(#b)" opacity=".3"/><path d="m104.654 65.91a36.226 36.226 0 0 0 -38.641-12.076l-49.913 16.1-16.1 28.177 90.164 15.295 16.1-28.981c3.22-5.635 2.415-12.075-1.61-18.516z" fill="url(#c)"/><path d="m88.553 94.085a36.226 36.226 0 0 0 -38.641-12.075l-49.912 16.1s42.667 32.202 75.673 24.152l2.415-.806c13.686-4.025 18.516-16.905 10.465-27.37z" fill="url(#d)"/></svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1 @@
|
||||
<svg enable-background="new 0 0 98.1 118" viewBox="0 0 98.1 118" xmlns="http://www.w3.org/2000/svg"><path d="m91.8 15.6c-10.9-15.7-32.6-20.3-48.2-10.4l-27.5 17.6c-7.5 4.7-12.7 12.4-14.2 21.1-1.3 7.3-.2 14.8 3.3 21.3-2.4 3.6-4 7.6-4.7 11.8-1.6 8.9.5 18.1 5.7 25.4 11 15.7 32.6 20.3 48.2 10.4l27.5-17.5c7.5-4.7 12.7-12.4 14.2-21.1 1.3-7.3.2-14.8-3.3-21.3 2.4-3.6 4-7.6 4.7-11.8 1.7-9-.4-18.2-5.7-25.5" fill="#ff3e00"/><path d="m40.9 103.9c-8.9 2.3-18.2-1.2-23.4-8.7-3.2-4.4-4.4-9.9-3.5-15.3.2-.9.4-1.7.6-2.6l.5-1.6 1.4 1c3.3 2.4 6.9 4.2 10.8 5.4l1 .3-.1 1c-.1 1.4.3 2.9 1.1 4.1 1.6 2.3 4.4 3.4 7.1 2.7.6-.2 1.2-.4 1.7-.7l27.4-17.5c1.4-.9 2.3-2.2 2.6-3.8s-.1-3.3-1-4.6c-1.6-2.3-4.4-3.3-7.1-2.6-.6.2-1.2.4-1.7.7l-10.5 6.7c-1.7 1.1-3.6 1.9-5.6 2.4-8.9 2.3-18.2-1.2-23.4-8.7-3.1-4.4-4.4-9.9-3.4-15.3.9-5.2 4.1-9.9 8.6-12.7l27.5-17.5c1.7-1.1 3.6-1.9 5.6-2.5 8.9-2.3 18.2 1.2 23.4 8.7 3.2 4.4 4.4 9.9 3.5 15.3-.2.9-.4 1.7-.7 2.6l-.5 1.6-1.4-1c-3.3-2.4-6.9-4.2-10.8-5.4l-1-.3.1-1c.1-1.4-.3-2.9-1.1-4.1-1.6-2.3-4.4-3.3-7.1-2.6-.6.2-1.2.4-1.7.7l-27.4 17.5c-1.4.9-2.3 2.2-2.6 3.8s.1 3.3 1 4.6c1.6 2.3 4.4 3.3 7.1 2.6.6-.2 1.2-.4 1.7-.7l10.5-6.7c1.7-1.1 3.6-1.9 5.6-2.5 8.9-2.3 18.2 1.2 23.4 8.7 3.2 4.4 4.4 9.9 3.5 15.3-.9 5.2-4.1 9.9-8.6 12.7l-27.5 17.5c-1.7 1.1-3.6 1.9-5.6 2.5" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg fill="none" height="65" viewBox="0 0 66 65" width="66" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="m33.5 59c-19.6338 0-26.5-6.8676-26.5-26.5 0-19.6331 6.8662-26.5 26.5-26.5s26.5 6.8669 26.5 26.5c0 19.6324-6.8662 26.5-26.5 26.5zm-14.2692-34.6538h28.5384v-4.077h-28.5384zm0 10.1923h20.3846v-4.077h-20.3846zm0 10.1923h24.4615v-4.077h-24.4615z" fill="#000" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 405 B |
@@ -0,0 +1 @@
|
||||
<svg fill="none" height="48" viewBox="0 0 48 48" width="48" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="6.48163" x2="39.05" y1="1.9759" y2="48.2064"><stop offset="0" stop-color="#49c7ff"/><stop offset="1" stop-color="#bd36ff"/></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="4.39484" x2="38.3093" y1="5.32467" y2="31.584"><stop offset="0" stop-color="#41d1ff"/><stop offset="1" stop-color="#bd34fe"/></linearGradient><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="21.8138" x2="26.2464" y1="13.7047" y2="28.807"><stop offset="0" stop-color="#ffea83"/><stop offset=".0833333" stop-color="#ffdd35"/><stop offset="1" stop-color="#ffa800"/></linearGradient><clipPath id="d"><path d="m0 0h48v48h-48z"/></clipPath><g clip-path="url(#d)"><path d="m5.03628 7.87818c-.28292-2.03863 1.11964-3.92352 3.13271-4.21003l25.51481-3.6314097c2.0131-.2865103 3.8743 1.1338597 4.1572 3.1724897l5.1227 36.91257c.2829 2.0386-1.1196 3.9235-3.1327 4.2101l-25.5148 3.6314c-2.0131.2865-3.8743-1.1339-4.1572-3.1725z" fill="url(#a)"/><path d="m6.85877 7.6188c-.14146-1.01932.55982-1.96177 1.56635-2.10502l25.51478-3.63141c1.0066-.14326 1.9372.56693 2.0787 1.58624l5.1226 36.91259c.1415 1.0193-.5598 1.9618-1.5663 2.105l-25.5148 3.6314c-1.0066.1433-1.9372-.5669-2.0787-1.5862z" fill="#fff"/><path d="m33.1857 14.9195-7.3352 19.2381c-.1514.3971-.6742.4724-.9328.1343l-12.5834-16.458c-.2817-.3684-.0126-.9.4463-.8815l10.1247.4083c.0645.0026.1291-.0066.1903-.027l9.4132-3.1345c.4343-.1446.8415.2886.6769.7203z" fill="url(#b)"/><path d="m27.0251 12.5756-7.0899 2.4671c-.1165.0405-.1908.1559-.1806.2804l.637 7.7399c.015.1823.1988.2998.3672.2347l1.9638-.7585c.1838-.071.3795.0746.3679.2736l-.1803 3.0775c-.012.2071.1991.3533.3848.2664l1.1998-.5614c.186-.0869.3972.0596.3847.2671l-.294 4.8355c-.0184.3025.3904.4088.5158.1342l.0837-.1834 4.2448-12.8473c.071-.2151-.133-.4205-.3451-.3474l-2.0328.7007c-.191.0658-.3844-.0961-.3565-.2986l.6869-4.9822c.028-.2027-.166-.3648-.3572-.2983z" fill="url(#c)"/></g></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 261.76 226.69" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(1.3333 0 0 -1.3333 161.096398 .001167)"><path d="m0 0-22.669-39.264-22.669 39.264h-75.491l98.16-170.02 98.16 170.02z" fill="#41b883"/><path d="m0 0-22.669-39.264-22.669 39.264h-36.227l58.896-102.01 58.896 102.01z" fill="#34495e"/></g></svg>
|
||||
|
After Width: | Height: | Size: 329 B |
|
After Width: | Height: | Size: 150 KiB |
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 122.52 122.523" xmlns="http://www.w3.org/2000/svg"><g fill="#21759b"><path d="m8.708 61.26c0 20.802 12.089 38.779 29.619 47.298l-25.069-68.686c-2.916 6.536-4.55 13.769-4.55 21.388z"/><path d="m96.74 58.608c0-6.495-2.333-10.993-4.334-14.494-2.664-4.329-5.161-7.995-5.161-12.324 0-4.831 3.664-9.328 8.825-9.328.233 0 .454.029.681.042-9.35-8.566-21.807-13.796-35.489-13.796-18.36 0-34.513 9.42-43.91 23.688 1.233.037 2.395.063 3.382.063 5.497 0 14.006-.667 14.006-.667 2.833-.167 3.167 3.994.337 4.329 0 0-2.847.335-6.015.501l19.138 56.925 11.501-34.493-8.188-22.434c-2.83-.166-5.511-.501-5.511-.501-2.832-.166-2.5-4.496.332-4.329 0 0 8.679.667 13.843.667 5.496 0 14.006-.667 14.006-.667 2.835-.167 3.168 3.994.337 4.329 0 0-2.853.335-6.015.501l18.992 56.494 5.242-17.517c2.272-7.269 4.001-12.49 4.001-16.989z"/><path d="m62.184 65.857-15.768 45.819c4.708 1.384 9.687 2.141 14.846 2.141 6.12 0 11.989-1.058 17.452-2.979-.141-.225-.269-.464-.374-.724z"/><path d="m107.376 36.046c.226 1.674.354 3.471.354 5.404 0 5.333-.996 11.328-3.996 18.824l-16.053 46.413c15.624-9.111 26.133-26.038 26.133-45.426.001-9.137-2.333-17.729-6.438-25.215z"/><path d="m61.262 0c-33.779 0-61.262 27.481-61.262 61.26 0 33.783 27.483 61.263 61.262 61.263 33.778 0 61.265-27.48 61.265-61.263-.001-33.779-27.487-61.26-61.265-61.26zm0 119.715c-32.23 0-58.453-26.223-58.453-58.455 0-32.23 26.222-58.451 58.453-58.451 32.229 0 58.45 26.221 58.45 58.451 0 32.232-26.221 58.455-58.45 58.455z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -1,7 +1,7 @@
|
||||
.feature {
|
||||
min-height: 90vh;
|
||||
|
||||
.content {
|
||||
& > .content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
@@ -1,27 +1,27 @@
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import './FeatureBase.scss'
|
||||
import './Base.scss'
|
||||
import { useIsElementVisible } from '../../hooks/visible'
|
||||
|
||||
interface FeatureBaseProps extends React.ComponentProps<'div'> {
|
||||
onVisibleChange?: (visible: boolean) => void
|
||||
}
|
||||
|
||||
export const FeatureBase: React.FC<FeatureBaseProps> = (props) => {
|
||||
export const Base: React.FC<FeatureBaseProps> = (props) => {
|
||||
const { className, onVisibleChange, ...otherProps } = props
|
||||
|
||||
const refElem = useRef<HTMLDivElement>(null)
|
||||
const isSlightFeatureVisible = useIsElementVisible(refElem)
|
||||
const isVisible = useIsElementVisible(refElem)
|
||||
|
||||
const classNames = [
|
||||
'feature',
|
||||
'container',
|
||||
isSlightFeatureVisible ? 'visible' : '',
|
||||
isVisible ? 'visible' : '',
|
||||
...(className?.split(' ') || []),
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
onVisibleChange && onVisibleChange(isSlightFeatureVisible)
|
||||
}, [onVisibleChange, isSlightFeatureVisible])
|
||||
onVisibleChange && onVisibleChange(isVisible)
|
||||
}, [onVisibleChange, isVisible])
|
||||
|
||||
return (
|
||||
<div ref={refElem} className={classNames.join(' ')} {...otherProps}>
|
||||
@@ -0,0 +1,33 @@
|
||||
.feature-block {
|
||||
min-height: 520px;
|
||||
padding: 64px 76px;
|
||||
border-radius: 32px;
|
||||
background-color: #e8f0fe;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > .content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
main {
|
||||
}
|
||||
|
||||
aside {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.feature-desc {
|
||||
margin: 20px 0;
|
||||
font-size: 22px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import './BlockBase.scss'
|
||||
import { useIsElementVisible } from '../../hooks/visible'
|
||||
|
||||
interface FeatureBaseProps extends React.ComponentProps<'div'> {
|
||||
onVisibleChange?: (visible: boolean) => void
|
||||
}
|
||||
|
||||
export const BlockBase: React.FC<FeatureBaseProps> = (props) => {
|
||||
const { className, onVisibleChange, ...otherProps } = props
|
||||
|
||||
const refElem = useRef<HTMLDivElement>(null)
|
||||
const isVisible = useIsElementVisible(refElem)
|
||||
|
||||
const classNames = ['feature-block', isVisible ? 'visible' : '', ...(className?.split(' ') || [])]
|
||||
|
||||
useEffect(() => {
|
||||
onVisibleChange && onVisibleChange(isVisible)
|
||||
}, [onVisibleChange, isVisible])
|
||||
|
||||
return (
|
||||
<div ref={refElem} className={classNames.join(' ')} {...otherProps}>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import './FeatureDesc.scss'
|
||||
import './Desc.scss'
|
||||
|
||||
export const FeatureDesc: React.FC<{
|
||||
export const Desc: React.FC<{
|
||||
children: React.ReactNode
|
||||
}> = ({ children }) => {
|
||||
return <div className="feature-desc">{children}</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
.feature-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
column-gap: 64px;
|
||||
row-gap: 40px;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import './Group.scss'
|
||||
|
||||
export const Group: React.FC<{
|
||||
children: React.ReactNode
|
||||
}> = ({ children }) => {
|
||||
return <div className="feature-group container">{children}</div>
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import './Title.scss'
|
||||
import { Reveal } from '../Reveal'
|
||||
|
||||
interface TitleProps {
|
||||
text: string
|
||||
color: string
|
||||
}
|
||||
|
||||
export const Title: React.FC<TitleProps> = (props) => {
|
||||
return (
|
||||
<Reveal>
|
||||
<div className="feature-title" style={{ color: props.color }}>
|
||||
{props.text.slice(0, 1)}
|
||||
{props.text.slice(1, props.text.length)}
|
||||
</div>
|
||||
</Reveal>
|
||||
)
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import './FeatureTitle.scss'
|
||||
import { Reveal } from './Reveal'
|
||||
|
||||
interface FeatureTitleProps {
|
||||
text: string
|
||||
color: string
|
||||
}
|
||||
|
||||
export const FeatureTitle: React.FC<FeatureTitleProps> = (props) => {
|
||||
return (
|
||||
<Reveal>
|
||||
<div className="feature-title" style={{ color: props.color }}>
|
||||
<span className="bold">{props.text.slice(0, 1)}</span>
|
||||
{props.text.slice(1, props.text.length)}
|
||||
</div>
|
||||
</Reveal>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
.frameworks-feature {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
padding: 20px 0;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 100px;
|
||||
|
||||
.title {
|
||||
width: 300px;
|
||||
font-size: 34px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin-left: 50px;
|
||||
line-height: 1.8;
|
||||
|
||||
.text-row:first-child {
|
||||
color: var(--color-font-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.methods {
|
||||
padding: 0 50px;
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 auto;
|
||||
gap: 15px;
|
||||
|
||||
.icon {
|
||||
height: 80px;
|
||||
width: 80px;
|
||||
background-position: center;
|
||||
background-size: 50%;
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 50%;
|
||||
transition: all 0.2s ease-out;
|
||||
|
||||
&.active,
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.04);
|
||||
box-shadow: 0 0 0 12px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import './Frameworks.scss'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import VueIcon from '../../assets/techs/vue.svg'
|
||||
import ReactIcon from '../../assets/techs/react.svg'
|
||||
import SolidIcon from '../../assets/techs/solid.svg'
|
||||
import SvelteIcon from '../../assets/techs/svelte.svg'
|
||||
import HexoIcon from '../../assets/techs/hexo.svg'
|
||||
import VitepressIcon from '../../assets/techs/vitepress.svg'
|
||||
import WordpressIcon from '../../assets/techs/wordpress.svg'
|
||||
import GoIcon from '../../assets/techs/go.svg'
|
||||
|
||||
const techs = [
|
||||
{
|
||||
name: 'Vue',
|
||||
icon: VueIcon,
|
||||
color: '#41b883',
|
||||
link: 'https://artalk.js.org/develop/import-framework.html#vue',
|
||||
},
|
||||
{
|
||||
name: 'React',
|
||||
icon: ReactIcon,
|
||||
color: '#116A92',
|
||||
link: 'https://artalk.js.org/develop/import-framework.html#react',
|
||||
},
|
||||
{
|
||||
name: 'SolidJS',
|
||||
icon: SolidIcon,
|
||||
color: '#27488B',
|
||||
link: 'https://artalk.js.org/develop/import-framework.html#solidjs',
|
||||
},
|
||||
{
|
||||
name: 'Svelte',
|
||||
icon: SvelteIcon,
|
||||
color: '#ff3e00',
|
||||
link: 'https://artalk.js.org/develop/import-framework.html#svelte',
|
||||
},
|
||||
{
|
||||
name: 'VitePress',
|
||||
icon: VitepressIcon,
|
||||
color: '#8F4CFF',
|
||||
link: 'https://artalk.js.org/develop/import-blog.html#vitepress',
|
||||
},
|
||||
{
|
||||
name: 'Hexo',
|
||||
icon: HexoIcon,
|
||||
color: '#136DC1',
|
||||
link: 'https://artalk.js.org/develop/import-blog.html#hexo',
|
||||
},
|
||||
{
|
||||
name: 'WordPress',
|
||||
icon: WordpressIcon,
|
||||
color: '#1C6189',
|
||||
link: 'https://artalk.js.org/develop/import-blog.html#wordpress',
|
||||
},
|
||||
{
|
||||
name: 'Go',
|
||||
icon: GoIcon,
|
||||
color: '#00acd7',
|
||||
link: 'https://artalk.js.org/guide/deploy.html',
|
||||
},
|
||||
]
|
||||
|
||||
export const FrameworksFeature: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const [activeIndex, setActiveIndex] = useState(-1)
|
||||
const [stop, setStop] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (stop) return
|
||||
const timer = setInterval(() => {
|
||||
setActiveIndex((i) => {
|
||||
if (i + 1 == techs.length) return -1
|
||||
return (i + 1) % techs.length
|
||||
})
|
||||
}, 2000)
|
||||
return () => clearInterval(timer)
|
||||
}, [stop])
|
||||
|
||||
const onMouseOver = (i: number) => {
|
||||
setStop(true)
|
||||
setActiveIndex(i)
|
||||
}
|
||||
|
||||
const onMouseOut = () => {
|
||||
setStop(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="frameworks-feature container">
|
||||
<div className="title">
|
||||
<div className="text-row">{t('use_artalk_with')}</div>
|
||||
<div
|
||||
className="text-row"
|
||||
style={{ color: activeIndex != -1 ? techs[activeIndex].color : undefined }}
|
||||
>
|
||||
{activeIndex == -1 ? t('any_website_or_blog') : techs[activeIndex].name}
|
||||
</div>
|
||||
</div>
|
||||
<div className="methods">
|
||||
{techs.map((tech, i) => (
|
||||
<a
|
||||
key={i}
|
||||
className={['icon', activeIndex == i ? 'active' : ''].join(' ')}
|
||||
style={{ backgroundImage: `url('${tech.icon.replace(/'/g, "\\'")}')` }}
|
||||
href={tech.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onMouseOver={() => onMouseOver(i)}
|
||||
onMouseOut={() => onMouseOut()}
|
||||
></a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
.full.feature {
|
||||
.full-feature {
|
||||
margin-top: 120px;
|
||||
padding-top: 100px;
|
||||
padding-bottom: 50px;
|
||||
margin-bottom: 100px;
|
||||
|
||||
.func-list {
|
||||
margin: 100px 0;
|
||||
margin-top: 30px;
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
@@ -20,7 +25,6 @@
|
||||
margin: 0 15px;
|
||||
flex: 1;
|
||||
border-radius: 20px;
|
||||
border: 3px solid var(--color-border);
|
||||
background: var(--color-bg);
|
||||
padding: 40px 50px;
|
||||
transition: transform 200ms cubic-bezier(0.75, 0.25, 0.25, 0.75);
|
||||
@@ -35,11 +39,22 @@
|
||||
place-items: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.icon-wrap {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
justify-content: center;
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
margin-right: 25px;
|
||||
}
|
||||
|
||||
svg {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.text {
|
||||
flex: 1;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import './Full.scss'
|
||||
import { Title } from '../FeatureComps/Title'
|
||||
import { Desc } from '../FeatureComps/Desc'
|
||||
import { Reveal } from '../Reveal'
|
||||
import { Base } from '../FeatureComps/Base'
|
||||
import { FolderIcon } from '../FeatureComps/FolderIcon'
|
||||
import { FullList } from './FullList'
|
||||
|
||||
export const FullFeature: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="app-grey-bg">
|
||||
<Base className="full-feature">
|
||||
<Title text={t('feature_full_title')} color="var(--color-font-secondary)" />
|
||||
|
||||
<div className="content">
|
||||
<main>
|
||||
<div className="desc">
|
||||
<Reveal delay={200}>
|
||||
<Desc>{t('feature_full_desc_line_1')}</Desc>
|
||||
</Reveal>
|
||||
</div>
|
||||
</main>
|
||||
<aside>
|
||||
<FolderIcon />
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<FullList />
|
||||
</Base>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import './FullFeature.scss'
|
||||
import { FeatureTitle } from '../FeatureTitle'
|
||||
import { FeatureDesc } from '../FeatureDesc'
|
||||
import { Reveal } from '../Reveal'
|
||||
import { FeatureBase } from './FeatureBase'
|
||||
import { FolderIcon } from './FullFeatureIcon'
|
||||
import { FullFeatureList } from './FullFeatureList'
|
||||
|
||||
export const FullFeature: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<FeatureBase className="full">
|
||||
<FeatureTitle text={t('feature_full_title')} color="var(--color-font-secondary)" />
|
||||
|
||||
<div className="content">
|
||||
<main>
|
||||
<div className="desc">
|
||||
<Reveal delay={200}>
|
||||
<FeatureDesc>{t('feature_full_desc_line_1')}</FeatureDesc>
|
||||
</Reveal>
|
||||
</div>
|
||||
</main>
|
||||
<aside>
|
||||
<FolderIcon />
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<FullFeatureList />
|
||||
</FeatureBase>
|
||||
)
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getFeatures, FeatureItem } from '../../features.ts'
|
||||
|
||||
export const FullFeatureList: React.FC = () => {
|
||||
export const FullList: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
// let func list item group by every two items
|
||||
@@ -19,7 +19,9 @@ export const FullFeatureList: React.FC = () => {
|
||||
{row.map((item, j) => (
|
||||
<a key={j} className="item" href={item.link} target="_blank" rel="noreferrer">
|
||||
<div className="header">
|
||||
<item.icon />
|
||||
<div className="icon-wrap">
|
||||
<item.icon />
|
||||
</div>
|
||||
<span className="text">{item.name}</span>
|
||||
</div>
|
||||
<div className="body">
|
||||
@@ -1,4 +1,4 @@
|
||||
.func.feature {
|
||||
.funcs-feature {
|
||||
.content .ship-icon {
|
||||
margin: 0 80px;
|
||||
margin-top: -70px;
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import './FuncFeature.scss'
|
||||
import './Funcs.scss'
|
||||
import { FaArrowRight } from 'react-icons/fa'
|
||||
import { FeatureTitle } from '../FeatureTitle'
|
||||
import { FeatureDesc } from '../FeatureDesc'
|
||||
import { Title } from '../FeatureComps/Title'
|
||||
import { Desc } from '../FeatureComps/Desc'
|
||||
import { Reveal } from '../Reveal'
|
||||
import { FeatureBase } from './FeatureBase'
|
||||
import { Base } from '../FeatureComps/Base'
|
||||
|
||||
export const FuncFeature: React.FC = () => {
|
||||
export const FuncsFeature: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const FuncGrps: { name: string; items: string[]; link?: string }[] = [
|
||||
@@ -122,13 +122,13 @@ export const FuncFeature: React.FC = () => {
|
||||
]
|
||||
|
||||
return (
|
||||
<FeatureBase className="func">
|
||||
<FeatureTitle text={t('feature_func_title')} color="var(--color-font)" />
|
||||
<Base className="funcs-feature">
|
||||
<Title text={t('feature_func_title')} color="var(--color-font)" />
|
||||
|
||||
<div className="content">
|
||||
<main>
|
||||
<Reveal delay={250}>
|
||||
<FeatureDesc>{t('feature_func_desc_line_1')}</FeatureDesc>
|
||||
<Desc>{t('feature_func_desc_line_1')}</Desc>
|
||||
</Reveal>
|
||||
</main>
|
||||
<aside>
|
||||
@@ -204,6 +204,6 @@ export const FuncFeature: React.FC = () => {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</FeatureBase>
|
||||
</Base>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
.grey-cards-feature {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
margin-bottom: 140px;
|
||||
|
||||
& > .section {
|
||||
flex: 1;
|
||||
height: 560px;
|
||||
border-radius: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--color-bg-grey-deep);
|
||||
padding: 40px;
|
||||
overflow: hidden;
|
||||
|
||||
.section-title {
|
||||
font-size: 22px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.section-desc {
|
||||
font-size: 20px;
|
||||
color: #919191;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.section-learn-more {
|
||||
color: #0066cc;
|
||||
display: inline-flex;
|
||||
gap: 10px;
|
||||
font-size: 20px;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
width: max-content;
|
||||
border-radius: 100px;
|
||||
transition: all 0.3s;
|
||||
padding: 10px 0;
|
||||
|
||||
&:hover {
|
||||
padding: 10px 18px;
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
svg {
|
||||
font-size: 1.2em;
|
||||
margin-right: -8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.window {
|
||||
position: relative;
|
||||
box-shadow: 0px 0px 40px 0px rgba(0, 0, 0, 0.06);
|
||||
border: 1px solid var(--color-border-light);
|
||||
background: var(--color-bg);
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
|
||||
&.text-window {
|
||||
width: calc(100% + 70px);
|
||||
}
|
||||
|
||||
&.terminal-window {
|
||||
width: calc(100% + 70px);
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.text-row {
|
||||
overflow: hidden;
|
||||
padding: 12px 24px;
|
||||
white-space: nowrap;
|
||||
border-radius: 8px;
|
||||
background: #efefef;
|
||||
color: var(--color-font);
|
||||
|
||||
&:nth-child(2) {
|
||||
background: #ecf2ff;
|
||||
color: #053dcc;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
background: #fff5ee;
|
||||
color: #f97709;
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
background: #e9fbfa;
|
||||
color: #108163;
|
||||
}
|
||||
|
||||
&:nth-child(5) {
|
||||
background: #feecf8;
|
||||
color: #ba006b;
|
||||
}
|
||||
}
|
||||
|
||||
.menubar {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin: 0 5px;
|
||||
|
||||
& > div {
|
||||
display: block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid transparent;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.close {
|
||||
background: #fb4646;
|
||||
border-color: #ea383a;
|
||||
}
|
||||
|
||||
.minimize {
|
||||
background: #fdb125;
|
||||
border-color: #e8a430;
|
||||
}
|
||||
|
||||
.zoom {
|
||||
background: #29c332;
|
||||
border-color: #36b537;
|
||||
}
|
||||
}
|
||||
|
||||
.docker-icon {
|
||||
position: absolute;
|
||||
top: -70px;
|
||||
right: 50px;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
|
||||
img {
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.window-body {
|
||||
padding: 10px 10px;
|
||||
|
||||
.cmd-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
padding: 10px 0;
|
||||
font-size: 18px;
|
||||
|
||||
.prompt {
|
||||
user-select: none;
|
||||
color: #919191;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.screenshot {
|
||||
width: calc(100% + 100px);
|
||||
border-radius: 16px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { FiArrowUpRight } from 'react-icons/fi'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import './GreyCards.scss'
|
||||
import LazyImage from '../LazyImage'
|
||||
|
||||
export const GreyCardsFeature: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="grey-cards-feature container">
|
||||
<div className="section">
|
||||
<div className="section-title">{t('concept_section_locales_title')}</div>
|
||||
<div className="section-desc">{t('concept_section_locales_desc')}</div>
|
||||
|
||||
<div className="window text-window">
|
||||
<div className="text-row">Artalk Self-hosted Comment System</div>
|
||||
<div className="text-row">Artalk 自托管评论系统</div>
|
||||
<div className="text-row">Artalk Auto-hébergé Commentaire Système</div>
|
||||
<div className="text-row">Artalk セルフホスティング コメント システム</div>
|
||||
<div className="text-row">Artalk 셀프 호스팅 댓글 시스템</div>
|
||||
<div className="text-row">Artalk Cамохостинг Комментарий Система</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="section">
|
||||
<div className="section-title">{t('concept_section_docker_title')}</div>
|
||||
<div className="section-desc">
|
||||
{t('concept_section_docker_desc_line_1')}
|
||||
<br />
|
||||
<br />
|
||||
{t('concept_section_docker_desc_line_2')}
|
||||
</div>
|
||||
<a
|
||||
className="section-learn-more"
|
||||
target="_blank"
|
||||
href="https://artalk.js.org/zh/guide/deploy.html"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{t('learn_more')}
|
||||
<FiArrowUpRight />
|
||||
</a>
|
||||
<div className="window terminal-window">
|
||||
<div className="docker-icon">
|
||||
<LazyImage src="/images/docker-folder.png" alt="Docker" />
|
||||
</div>
|
||||
<div className="menubar">
|
||||
<div className="close"></div>
|
||||
<div className="minimize"></div>
|
||||
<div className="zoom"></div>
|
||||
</div>
|
||||
<div className="window-body">
|
||||
<div className="cmd-row">
|
||||
<div className="prompt">$</div>
|
||||
<div className="command">docker pull artalk/artalk</div>
|
||||
</div>
|
||||
<div className="cmd-row">
|
||||
<div className="prompt">$</div>
|
||||
<div className="command">docker run -d -p 8080:23366 artalk/artalk</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="section">
|
||||
<div className="section-title">{t('concept_section_open_source_title')}</div>
|
||||
<div className="section-desc">
|
||||
{t('concept_section_open_source_desc_line_1')}
|
||||
<br />
|
||||
<br />
|
||||
{t('concept_section_open_source_desc_line_2', { year: new Date().getFullYear() - 2018 })}
|
||||
</div>
|
||||
<LazyImage
|
||||
src="/images/github-repo-page.webp"
|
||||
alt="GitHub Repository"
|
||||
className="screenshot"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
.plugins-feature {
|
||||
background-color: #174ea6;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-image: url(/images/plugins-bg.jpg);
|
||||
background-size: cover;
|
||||
|
||||
@keyframes plugin-icon-anim {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0.5) translateZ(-100px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1) rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
.feature-desc {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.visible .left-right {
|
||||
.stamps svg {
|
||||
animation: plugin-icon-anim 0.5s ease forwards;
|
||||
}
|
||||
}
|
||||
|
||||
.left-right {
|
||||
gap: 50px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
main {
|
||||
flex: 40%;
|
||||
}
|
||||
|
||||
aside {
|
||||
flex: 60%;
|
||||
}
|
||||
|
||||
.stamps {
|
||||
pointer-events: none;
|
||||
margin-top: 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
svg {
|
||||
font-size: 36px;
|
||||
color: #acccff;
|
||||
opacity: 0;
|
||||
transition:
|
||||
opacity 0.5s ease,
|
||||
transform 0.5s ease;
|
||||
|
||||
&:nth-child(1) {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.row:not(:last-child) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.row:nth-child(odd) {
|
||||
margin-left: -50px;
|
||||
}
|
||||
|
||||
.row:nth-child(even) {
|
||||
margin-right: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { PiMoonFill, PiSmileyWinkBold } from 'react-icons/pi'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import './Plugins.scss'
|
||||
import {
|
||||
TbCardsFilled,
|
||||
TbLayoutSidebarRightExpandFilled,
|
||||
TbMailFilled,
|
||||
TbPlug,
|
||||
TbSocial,
|
||||
TbTerminal,
|
||||
TbTransformFilled,
|
||||
} from 'react-icons/tb'
|
||||
import { BiSolidBadgeCheck } from 'react-icons/bi'
|
||||
import { LuNewspaper } from 'react-icons/lu'
|
||||
import { FaArrowTrendUp } from 'react-icons/fa6'
|
||||
import { FaMarkdown } from 'react-icons/fa'
|
||||
import { RiRobot2Fill } from 'react-icons/ri'
|
||||
import { BlockBase } from '../FeatureComps/BlockBase'
|
||||
|
||||
export const Plugins: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<BlockBase className="plugins-feature">
|
||||
<div className="feature-title" style={{ color: '#fff' }}>
|
||||
{t('feature_community_title')}
|
||||
</div>
|
||||
<div className="left-right">
|
||||
<main>
|
||||
<div className="feature-desc">{t('feature_community_desc')}</div>
|
||||
<div className="stamps">
|
||||
<div className="row">
|
||||
<PiSmileyWinkBold />
|
||||
<TbCardsFilled />
|
||||
<BiSolidBadgeCheck />
|
||||
<TbPlug />
|
||||
</div>
|
||||
<div className="row">
|
||||
<LuNewspaper />
|
||||
<TbLayoutSidebarRightExpandFilled />
|
||||
<TbSocial />
|
||||
<PiMoonFill />
|
||||
</div>
|
||||
<div className="row">
|
||||
<TbMailFilled />
|
||||
<FaArrowTrendUp />
|
||||
<TbTransformFilled />
|
||||
<FaMarkdown />
|
||||
</div>
|
||||
<div className="row">
|
||||
<TbTerminal />
|
||||
<RiRobot2Fill />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<aside></aside>
|
||||
</div>
|
||||
</BlockBase>
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
.feature.quick {
|
||||
.quick-feature {
|
||||
background-color: #f2f9ff;
|
||||
|
||||
.deploy-links {
|
||||
margin: 80px 0;
|
||||
display: flex;
|
||||
@@ -10,7 +12,7 @@
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
place-items: flex-end;
|
||||
font-size: 2rem;
|
||||
font-size: 1.5rem;
|
||||
color: var(--color-font);
|
||||
|
||||
&:not(:last-child) {
|
||||
@@ -30,7 +32,7 @@
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-size: 3rem;
|
||||
font-size: 2.4rem;
|
||||
margin-right: 0.2em;
|
||||
}
|
||||
|
||||
@@ -39,6 +41,7 @@
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
font-size: 2rem;
|
||||
margin-left: auto;
|
||||
position: relative;
|
||||
display: flex;
|
||||
@@ -71,9 +74,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
margin-top: -20px;
|
||||
}
|
||||
|
||||
.airplane-wrap {
|
||||
margin-top: 100px;
|
||||
margin: 0 80px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.airplane {
|
||||
@@ -93,52 +99,4 @@
|
||||
animation: airplaneAnim 2s cubic-bezier(0.075, 0.82, 0.165, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.extra-desc {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-direction: row;
|
||||
margin: 250px 100px;
|
||||
|
||||
.golang-icon {
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
|
||||
.self-compile {
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
.docker-url {
|
||||
overflow: hidden;
|
||||
border-radius: 5px;
|
||||
font-size: 1.5rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
margin-bottom: 1.5em;
|
||||
background: var(--color-bg-grey);
|
||||
color: #67757f;
|
||||
white-space: nowrap;
|
||||
|
||||
.content {
|
||||
padding: 15px 30px;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
place-items: center;
|
||||
padding: 15px 25px;
|
||||
height: 100%;
|
||||
background: #00acd7;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
|
||||
&.copied {
|
||||
background: #50b88e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import React from 'react'
|
||||
import './Quick.scss'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FiArrowUpRight } from 'react-icons/fi'
|
||||
import { BlockBase } from '../FeatureComps/BlockBase'
|
||||
|
||||
export const QuickFeature: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const ArrowIcon = (
|
||||
<div className="arrow-icon">
|
||||
<div className="block" />
|
||||
<FiArrowUpRight size="2em" />
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<BlockBase className="quick-feature">
|
||||
<div className="feature-title" style={{ color: '#5DADEC' }}>
|
||||
{t('feature_swift_title')}
|
||||
|
||||
<div className="airplane-wrap">
|
||||
<svg
|
||||
width="80"
|
||||
height="80"
|
||||
viewBox="0 0 361 343"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g className="airplane">
|
||||
<path
|
||||
d="M327.5 133.04C325.317 135.228 321.788 135.228 319.605 133.04L311.71 125.145C309.522 122.962 309.522 119.427 311.71 117.25L327.5 101.46C329.683 99.2772 333.212 99.2772 335.395 101.46L343.29 109.355C345.478 111.538 345.478 115.072 343.29 117.25L327.5 133.04ZM243.75 49.2896C241.567 51.4783 238.038 51.4783 235.855 49.2896L227.96 41.3948C225.772 39.2117 225.772 35.6831 227.96 33.5L243.75 17.7103C245.933 15.5272 249.462 15.5272 251.645 17.7103L259.54 25.6051C261.728 27.7882 261.728 31.3169 259.54 33.5L243.75 49.2896Z"
|
||||
fill="#66757F"
|
||||
/>
|
||||
<path
|
||||
d="M171.167 122.833C182.333 122.833 232.583 128.417 232.583 128.417C232.583 128.417 238.167 178.667 238.167 189.833C238.167 201 227 201 221.417 195.417C215.833 189.833 199.083 161.917 199.083 161.917C199.083 161.917 171.167 145.167 165.583 139.583C160 134 160 122.833 171.167 122.833ZM182.333 33.7178C199.083 33.5 321.917 39.0833 321.917 39.0833C321.917 39.0833 327.076 161.917 327.288 178.667C327.5 195.417 311.861 201.011 305.725 178.672C299.589 156.333 277.25 83.75 277.25 83.75C277.25 83.75 199.335 59.0884 182.305 55.2638C160 50.25 165.578 33.9299 182.333 33.7178Z"
|
||||
fill="#55ACEE"
|
||||
/>
|
||||
<path
|
||||
d="M310.75 16.75C321.917 5.58331 349.833 -2.00272e-05 355.417 5.58331C361 11.1666 355.417 39.0833 344.25 50.25C333.083 61.4166 238.167 150.75 238.167 150.75C238.167 150.75 201.875 175.875 193.5 167.5C185.125 159.125 210.25 122.833 210.25 122.833C210.25 122.833 299.583 27.9166 310.75 16.75Z"
|
||||
fill="#CCD6DD"
|
||||
/>
|
||||
<path
|
||||
d="M238.167 122.833C238.167 122.833 240.958 125.625 215.833 150.75C190.708 175.875 187.917 173.083 187.917 173.083C187.917 173.083 185.125 170.292 210.25 145.167C235.375 120.042 238.167 122.833 238.167 122.833ZM321.917 22.3333C331.168 22.3333 338.667 29.8317 338.667 39.0833H343.161C343.798 37.3246 344.25 35.4821 344.25 33.5C344.25 24.2484 336.752 16.75 327.5 16.75C325.518 16.75 323.675 17.2022 321.917 17.8387V22.3333Z"
|
||||
fill="#66757F"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="content">
|
||||
<main>
|
||||
<div className="deploy-links">
|
||||
<a className="item" href={t('deploy_bin_link')} target="_blank" rel="noreferrer">
|
||||
<span className="bold">{t('deploy_bin')}</span> <span>{t('deploy_bin_sub')}</span>
|
||||
{ArrowIcon}
|
||||
</a>
|
||||
<a className="item" href={t('deploy_docker_link')} target="_blank" rel="noreferrer">
|
||||
<span className="bold">{t('deploy_docker')}</span>{' '}
|
||||
<span>{t('deploy_docker_sub')}</span>
|
||||
{ArrowIcon}
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</BlockBase>
|
||||
)
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
import React from 'react'
|
||||
import './QuickFeature.scss'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FiArrowUpRight } from 'react-icons/fi'
|
||||
import { FeatureTitle } from '../FeatureTitle'
|
||||
import { LearnMoreLink } from '../LearnMoreLink'
|
||||
import { FeatureDesc } from '../FeatureDesc'
|
||||
import { Reveal } from '../Reveal'
|
||||
import { FeatureBase } from './FeatureBase'
|
||||
|
||||
export const QuickFeature: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const ArrowIcon = (
|
||||
<div className="arrow-icon">
|
||||
<div className="block" />
|
||||
<FiArrowUpRight size="2em" />
|
||||
</div>
|
||||
)
|
||||
|
||||
const [isCopied, setIsCopied] = React.useState(false)
|
||||
|
||||
let copiedResetTimer: any
|
||||
|
||||
const copyDockerURLHandler = (e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
clearTimeout(copiedResetTimer)
|
||||
setIsCopied(false)
|
||||
|
||||
const dockerURL = 'docker pull artalk/artalk-go'
|
||||
if (!navigator.clipboard.writeText) {
|
||||
alert('Browser does not support clipboard API')
|
||||
return
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(dockerURL)
|
||||
setIsCopied(true)
|
||||
|
||||
copiedResetTimer = setTimeout(() => {
|
||||
setIsCopied(false)
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
return (
|
||||
<FeatureBase className="quick">
|
||||
<FeatureTitle text={t('feature_swift_title')} color="#5DADEC" />
|
||||
|
||||
<div className="content">
|
||||
<main>
|
||||
<div className="deploy-links">
|
||||
<a className="item" href={t('deploy_bin_link')} target="_blank" rel="noreferrer">
|
||||
<span className="bold">{t('deploy_bin')}</span> <span>{t('deploy_bin_sub')}</span>
|
||||
{ArrowIcon}
|
||||
</a>
|
||||
<a className="item" href={t('deploy_docker_link')} target="_blank" rel="noreferrer">
|
||||
<span className="bold">{t('deploy_docker')}</span>{' '}
|
||||
<span>{t('deploy_docker_sub')}</span>
|
||||
{ArrowIcon}
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<aside>
|
||||
<div className="airplane-wrap">
|
||||
<svg
|
||||
width="361"
|
||||
height="343"
|
||||
viewBox="0 0 361 343"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g className="airplane">
|
||||
<path
|
||||
d="M327.5 133.04C325.317 135.228 321.788 135.228 319.605 133.04L311.71 125.145C309.522 122.962 309.522 119.427 311.71 117.25L327.5 101.46C329.683 99.2772 333.212 99.2772 335.395 101.46L343.29 109.355C345.478 111.538 345.478 115.072 343.29 117.25L327.5 133.04ZM243.75 49.2896C241.567 51.4783 238.038 51.4783 235.855 49.2896L227.96 41.3948C225.772 39.2117 225.772 35.6831 227.96 33.5L243.75 17.7103C245.933 15.5272 249.462 15.5272 251.645 17.7103L259.54 25.6051C261.728 27.7882 261.728 31.3169 259.54 33.5L243.75 49.2896Z"
|
||||
fill="#66757F"
|
||||
/>
|
||||
<path
|
||||
d="M171.167 122.833C182.333 122.833 232.583 128.417 232.583 128.417C232.583 128.417 238.167 178.667 238.167 189.833C238.167 201 227 201 221.417 195.417C215.833 189.833 199.083 161.917 199.083 161.917C199.083 161.917 171.167 145.167 165.583 139.583C160 134 160 122.833 171.167 122.833ZM182.333 33.7178C199.083 33.5 321.917 39.0833 321.917 39.0833C321.917 39.0833 327.076 161.917 327.288 178.667C327.5 195.417 311.861 201.011 305.725 178.672C299.589 156.333 277.25 83.75 277.25 83.75C277.25 83.75 199.335 59.0884 182.305 55.2638C160 50.25 165.578 33.9299 182.333 33.7178Z"
|
||||
fill="#55ACEE"
|
||||
/>
|
||||
<path
|
||||
d="M310.75 16.75C321.917 5.58331 349.833 -2.00272e-05 355.417 5.58331C361 11.1666 355.417 39.0833 344.25 50.25C333.083 61.4166 238.167 150.75 238.167 150.75C238.167 150.75 201.875 175.875 193.5 167.5C185.125 159.125 210.25 122.833 210.25 122.833C210.25 122.833 299.583 27.9166 310.75 16.75Z"
|
||||
fill="#CCD6DD"
|
||||
/>
|
||||
<path
|
||||
d="M238.167 122.833C238.167 122.833 240.958 125.625 215.833 150.75C190.708 175.875 187.917 173.083 187.917 173.083C187.917 173.083 185.125 170.292 210.25 145.167C235.375 120.042 238.167 122.833 238.167 122.833ZM321.917 22.3333C331.168 22.3333 338.667 29.8317 338.667 39.0833H343.161C343.798 37.3246 344.25 35.4821 344.25 33.5C344.25 24.2484 336.752 16.75 327.5 16.75C325.518 16.75 323.675 17.2022 321.917 17.8387V22.3333Z"
|
||||
fill="#66757F"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
{/* Golang Description */}
|
||||
<div className="extra-desc">
|
||||
<div className="left">
|
||||
<div className="text">
|
||||
<Reveal>
|
||||
<FeatureDesc>
|
||||
{t('feature_swift_desc_line_1')}
|
||||
<br />
|
||||
{t('feature_swift_desc_line_2')}
|
||||
<br />
|
||||
</FeatureDesc>
|
||||
</Reveal>
|
||||
</div>
|
||||
<a className="docker-url" href={t('docker_hub_link')} target="_blank" rel="noreferrer">
|
||||
<div className="content">docker pull artalk/artalk-go</div>
|
||||
<div
|
||||
className={['copy-btn', isCopied ? 'copied' : ''].join(' ')}
|
||||
onClick={copyDockerURLHandler}
|
||||
>
|
||||
{isCopied ? t('copied') : t('copy')}
|
||||
</div>
|
||||
</a>
|
||||
<div className="self-compile">
|
||||
<LearnMoreLink
|
||||
prompt={t('feature_swift_self_compile')}
|
||||
link={t('self_compile_guide_link')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="golang-icon">
|
||||
<svg
|
||||
width="292"
|
||||
height="109"
|
||||
viewBox="0 0 292 109"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_406_120)">
|
||||
<path
|
||||
d="M22.035 32.9701C21.4664 32.9701 21.3242 32.6858 21.6086 32.2595L24.594 28.4225C24.8783 27.9961 25.5891 27.7119 26.1577 27.7119H76.9094C77.4781 27.7119 77.6203 28.1383 77.3359 28.5646L74.9192 32.2595C74.6349 32.6858 73.924 33.1122 73.4976 33.1122L22.035 32.9701Z"
|
||||
fill="#00ACD7"
|
||||
/>
|
||||
<path
|
||||
d="M0.568647 46.0443C0 46.0443 -0.142162 45.7601 0.142162 45.3337L3.12756 41.4967C3.41188 41.0704 4.12269 40.7861 4.69133 40.7861H69.517C70.0857 40.7861 70.37 41.2125 70.2278 41.6388L69.0906 45.0495C68.9484 45.6179 68.3797 45.9022 67.8111 45.9022L0.568647 46.0443Z"
|
||||
fill="#00ACD7"
|
||||
/>
|
||||
<path
|
||||
d="M34.9718 59.1185C34.4031 59.1185 34.2609 58.6922 34.5453 58.2658L36.5355 54.713C36.8199 54.2867 37.3885 53.8604 37.9571 53.8604H66.3895C66.9581 53.8604 67.2424 54.2867 67.2424 54.8551L66.9581 58.2658C66.9581 58.8343 66.3895 59.2606 65.963 59.2606L34.9718 59.1185Z"
|
||||
fill="#00ACD7"
|
||||
/>
|
||||
<path
|
||||
d="M182.536 30.412C173.579 32.6857 167.466 34.3911 158.652 36.6649C156.52 37.2333 156.378 37.3754 154.53 35.2438C152.397 32.8279 150.834 31.2646 147.848 29.8435C138.892 25.438 130.22 26.717 122.117 31.9752C112.45 38.2281 107.474 47.4654 107.616 58.9765C107.759 70.3455 115.577 79.7249 126.808 81.2881C136.475 82.5671 144.578 79.1564 150.976 71.9087C152.255 70.3455 153.392 68.6401 154.814 66.6506C149.696 66.6506 143.299 66.6506 127.377 66.6506C124.391 66.6506 123.681 64.8031 124.676 62.3872C126.524 57.9817 129.936 50.5919 131.926 46.897C132.353 46.0443 133.348 44.6232 135.48 44.6232C142.73 44.6232 169.457 44.6232 187.227 44.6232C186.943 48.4602 186.943 52.2972 186.374 56.1343C184.81 66.3663 180.972 75.7457 174.717 83.9882C164.481 97.4889 151.118 105.873 134.201 108.147C120.269 109.995 107.332 107.295 95.9591 98.7679C85.4392 90.8096 79.4684 80.2933 77.9046 67.219C76.0565 51.7288 80.6057 37.8018 89.9883 25.5801C100.082 12.3637 113.445 3.97909 129.794 0.994739C143.157 -1.42117 155.951 0.142066 167.466 7.95823C175.001 12.9322 180.403 19.7535 183.957 27.996C184.81 29.2751 184.241 29.9856 182.536 30.412Z"
|
||||
fill="#00ACD7"
|
||||
/>
|
||||
<path
|
||||
d="M229.591 109C216.654 108.716 204.855 105.021 194.904 96.4941C186.516 89.2464 181.256 80.0091 179.55 69.0664C176.991 53.0078 181.398 38.7965 191.065 26.1486C201.443 12.5058 213.953 5.4002 230.871 2.41584C245.371 -0.142175 259.019 1.27895 271.387 9.66356C282.617 17.3376 289.583 27.7118 291.431 41.3546C293.848 60.5397 288.304 76.172 275.083 89.5306C265.7 99.0521 254.185 105.021 240.964 107.721C237.126 108.431 233.287 108.574 229.591 109ZM263.426 51.5866C263.283 49.7392 263.283 48.3181 262.999 46.8969C260.44 32.8278 247.503 24.8696 233.998 27.996C220.777 30.9804 212.247 39.365 209.12 52.7235C206.561 63.8083 211.963 75.0351 222.199 79.5827C230.018 82.9934 237.836 82.5671 245.371 78.7301C256.602 72.9035 262.715 63.8083 263.426 51.5866Z"
|
||||
fill="#00ACD7"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_406_120">
|
||||
<rect width="292" height="109" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</FeatureBase>
|
||||
)
|
||||
}
|
||||
@@ -1,11 +1,41 @@
|
||||
.safe.feature {
|
||||
.m1 {
|
||||
background: #9fbef9;
|
||||
color: #133995;
|
||||
}
|
||||
|
||||
.m2 {
|
||||
}
|
||||
|
||||
.safe-feature {
|
||||
background: #fef5d8;
|
||||
|
||||
& > .content {
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
|
||||
& > main {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
|
||||
.feature-desc {
|
||||
margin-left: auto;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.app-learn-more-link {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
margin-top: 80px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.data-icon {
|
||||
margin: 80px 0;
|
||||
margin-left: 80px;
|
||||
margin-left: 40px;
|
||||
margin-top: 35px;
|
||||
}
|
||||
|
||||
&.visible {
|
||||
@@ -45,6 +75,6 @@
|
||||
}
|
||||
|
||||
.report-audit {
|
||||
margin-top: 1.5em;
|
||||
margin-top: 2em;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,25 @@
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import './SafeFeature.scss'
|
||||
import { FeatureTitle } from '../FeatureTitle'
|
||||
import { FeatureDesc } from '../FeatureDesc'
|
||||
import './Safe.scss'
|
||||
import { Title } from '../FeatureComps/Title'
|
||||
import { Desc } from '../FeatureComps/Desc'
|
||||
import { Reveal } from '../Reveal'
|
||||
import { LearnMoreLink } from '../LearnMoreLink'
|
||||
import { FeatureBase } from './FeatureBase'
|
||||
import { BlockBase } from '../FeatureComps/BlockBase'
|
||||
|
||||
export const SafeFeature: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<FeatureBase className="safe">
|
||||
<FeatureTitle text={t('feature_safe_title')} color="#FFAC33" />
|
||||
<BlockBase className="safe-feature">
|
||||
<Title text={t('feature_safe_title')} color="#FFAC33" />
|
||||
|
||||
<div className="content">
|
||||
<aside>
|
||||
<svg
|
||||
className="data-icon"
|
||||
width="336"
|
||||
height="407"
|
||||
width="236"
|
||||
height="257"
|
||||
viewBox="0 0 336 407"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -46,7 +46,7 @@ export const SafeFeature: React.FC = () => {
|
||||
</svg>
|
||||
</aside>
|
||||
<main>
|
||||
<FeatureDesc>
|
||||
<Desc>
|
||||
<Reveal>
|
||||
{t('feature_safe_desc_line_1')}
|
||||
<br />
|
||||
@@ -62,9 +62,9 @@ export const SafeFeature: React.FC = () => {
|
||||
linkText={t('feature_safe_report_audit_btn')}
|
||||
/>
|
||||
</div>
|
||||
</FeatureDesc>
|
||||
</Desc>
|
||||
</main>
|
||||
</div>
|
||||
</FeatureBase>
|
||||
</BlockBase>
|
||||
)
|
||||
}
|
||||
@@ -1,15 +1,25 @@
|
||||
.feature.slight {
|
||||
.slight-feature {
|
||||
background-color: #f1f8e9;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover .screenshot {
|
||||
transform: translateY(-20px) scale(1.02);
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.progress-wrap {
|
||||
width: 100%;
|
||||
margin-top: 80px;
|
||||
margin-bottom: 50px;
|
||||
|
||||
.progress {
|
||||
margin-bottom: 15px;
|
||||
position: relative;
|
||||
height: 24px;
|
||||
height: 22px;
|
||||
width: 100%;
|
||||
background: var(--color-bg-grey);
|
||||
background: var(--color-bg-transparent);
|
||||
border-radius: 80px;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -23,6 +33,8 @@
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
@@ -32,7 +44,6 @@
|
||||
|
||||
.size {
|
||||
color: #77b255;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.percent {
|
||||
@@ -41,6 +52,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
.screenshot {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 65px;
|
||||
margin-bottom: -200px;
|
||||
transition: transform 0.5s cubic-bezier(0.23, 1, 0.32, 1);
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
&.visible {
|
||||
.leaf-wrap {
|
||||
@keyframes windAnim {
|
||||
@@ -0,0 +1,83 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Desc } from '../FeatureComps/Desc'
|
||||
import { BlockBase } from '../FeatureComps/BlockBase'
|
||||
import './Slight.scss'
|
||||
|
||||
export const SlightFeature: React.FC = () => {
|
||||
const [percent, setPercent] = useState(0)
|
||||
const [visible, setVisible] = useState(false)
|
||||
const { t } = useTranslation()
|
||||
|
||||
const onVisibleChange = (visible: boolean) => {
|
||||
setVisible(visible)
|
||||
if (visible) {
|
||||
const fn = setInterval(() => {
|
||||
setPercent((prev) => {
|
||||
const next = Math.round(prev + 0.5)
|
||||
if (next <= 100) {
|
||||
return next
|
||||
} else {
|
||||
clearInterval(fn)
|
||||
return 100
|
||||
}
|
||||
})
|
||||
}, 40)
|
||||
} else {
|
||||
setPercent(0)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<BlockBase className="slight-feature" onVisibleChange={onVisibleChange}>
|
||||
<div className="feature-title" style={{ color: '#77B255' }}>
|
||||
{t('feature_slight_title')}
|
||||
<svg
|
||||
className="leaf-wrap"
|
||||
width="80"
|
||||
height="80"
|
||||
viewBox="0 0 338 330"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g className="leaf">
|
||||
<path
|
||||
d="M100.05 227.714C129.808 257.472 198.146 298.863 275.608 227.298C231.821 218.875 224.117 163.163 194.353 133.411C164.595 103.647 123.073 96.9183 93.3094 126.676C63.5515 156.434 70.2866 197.95 100.05 227.714Z"
|
||||
fill="#A6D388"
|
||||
/>
|
||||
<path
|
||||
d="M208.733 235.204C245.16 244.917 272.089 230.817 274.573 228.332C274.573 228.332 252.068 238.741 212.995 219.558C218.654 212.353 235.905 202.266 235.905 202.266C232.617 200.209 211.663 200.928 197.355 210.76C190.471 206.403 183.183 201.148 175.521 194.859L180.514 189.681C199.834 169.565 221.793 174.321 221.793 174.321C218 170.677 188.51 153.866 164.827 178.482L161.183 182.275C158.651 179.903 156.095 177.448 153.491 174.844L142.494 163.847L148.819 157.516C168.56 137.78 197.718 137.002 197.718 137.002C193.996 133.274 156.921 121.255 132.78 145.395L128.405 149.764L79.1141 100.461C75.3928 96.734 69.0561 97.0431 65.3408 100.758C61.6196 104.48 61.3164 110.81 65.0376 114.532L114.335 163.829L113.984 164.18C89.8438 188.326 99.8661 227.393 103.593 231.12C103.593 231.12 106.375 199.959 126.117 180.224L128.417 177.917L149.229 198.735C149.568 199.074 149.9 199.365 150.239 199.704C135.824 228.219 157.545 261.169 161.879 263.809C161.879 263.809 153.17 235.733 164.732 212.478C173.803 219.582 182.684 224.932 191.226 228.855C191.72 246.647 207.318 262.739 211.104 264.112C211.104 264.112 205.689 246.261 207.93 237.439L208.733 235.204Z"
|
||||
fill="#77B255"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
className="wind"
|
||||
d="M193.271 117.444C190.917 117.444 188.688 116.03 187.755 113.711C186.536 110.668 188.004 107.22 191.042 105.989C192.029 105.585 214.915 95.8958 217.293 69.3539C217.59 66.0904 220.408 63.7007 223.742 63.9623C227.018 64.2535 229.425 67.1425 229.134 70.412C226.09 104.361 196.731 116.511 195.482 117.01C194.757 117.308 194.008 117.444 193.271 117.444ZM222.993 141.222C220.532 141.222 218.232 139.683 217.382 137.233C216.306 134.142 217.929 130.766 221.008 129.678C222.125 129.274 247.907 119.507 252.989 81.005C253.417 77.7534 256.324 75.4469 259.659 75.8928C262.916 76.3208 265.205 79.3049 264.771 82.5624C258.702 128.632 226.322 140.42 224.949 140.895C224.301 141.115 223.641 141.222 222.993 141.222ZM240.833 176.889C238.734 176.889 236.707 175.771 235.619 173.804C234.038 170.933 235.09 167.318 237.961 165.737C238.122 165.648 254.315 156.606 259.367 144.825C260.663 141.805 264.171 140.414 267.167 141.704C270.186 142.994 271.583 146.489 270.287 149.509C263.612 165.083 244.494 175.712 243.686 176.158C242.776 176.651 241.802 176.889 240.833 176.889Z"
|
||||
fill="#5DADEC"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="content">
|
||||
<Desc>
|
||||
{t('feature_slight_desc_line_1')}
|
||||
{t('feature_slight_desc_line_2')}
|
||||
</Desc>
|
||||
|
||||
<div className="progress-wrap">
|
||||
<div className="text">
|
||||
<div className="size">40KB</div>
|
||||
<div className="percent">{percent}%</div>
|
||||
</div>
|
||||
<div className="progress">
|
||||
<div className="bar" style={{ width: `${percent >= 100 || visible ? 100 : 0}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="screenshot">
|
||||
<img src="/images/lighthouse.webp" alt="screenshot" />
|
||||
</div>
|
||||
</div>
|
||||
</BlockBase>
|
||||
)
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FeatureTitle } from '../FeatureTitle'
|
||||
import { Reveal } from '../Reveal'
|
||||
import { FeatureDesc } from '../FeatureDesc'
|
||||
import { FeatureBase } from './FeatureBase'
|
||||
import './SlightFeature.scss'
|
||||
|
||||
export const SlightFeature: React.FC = () => {
|
||||
const [percent, setPercent] = useState(0)
|
||||
const [visible, setVisible] = useState(false)
|
||||
const { t } = useTranslation()
|
||||
|
||||
const onVisibleChange = (visible: boolean) => {
|
||||
setVisible(visible)
|
||||
if (visible) {
|
||||
const fn = setInterval(() => {
|
||||
setPercent((prev) => {
|
||||
const next = Math.round(prev + 0.5)
|
||||
if (next <= 100) {
|
||||
return next
|
||||
} else {
|
||||
clearInterval(fn)
|
||||
return 100
|
||||
}
|
||||
})
|
||||
}, 40)
|
||||
} else {
|
||||
setPercent(0)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<FeatureBase className="slight" onVisibleChange={onVisibleChange}>
|
||||
<FeatureTitle text={t('feature_slight_title')} color="#77B255" />
|
||||
|
||||
<div className="content">
|
||||
<main>
|
||||
<div className="progress-wrap">
|
||||
<div className="progress">
|
||||
<div className="bar" style={{ width: `${percent >= 100 || visible ? 100 : 0}%` }} />
|
||||
</div>
|
||||
<div className="text">
|
||||
<div className="size">40KB</div>
|
||||
<div className="percent">{percent}%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Reveal delay={200}>
|
||||
<FeatureDesc>
|
||||
{t('feature_slight_desc_line_1')}
|
||||
<br />
|
||||
{t('feature_slight_desc_line_2')}
|
||||
</FeatureDesc>
|
||||
</Reveal>
|
||||
</main>
|
||||
|
||||
<aside>
|
||||
<svg
|
||||
className="leaf-wrap"
|
||||
width="338"
|
||||
height="330"
|
||||
viewBox="0 0 338 330"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g className="leaf">
|
||||
<path
|
||||
d="M100.05 227.714C129.808 257.472 198.146 298.863 275.608 227.298C231.821 218.875 224.117 163.163 194.353 133.411C164.595 103.647 123.073 96.9183 93.3094 126.676C63.5515 156.434 70.2866 197.95 100.05 227.714Z"
|
||||
fill="#A6D388"
|
||||
/>
|
||||
<path
|
||||
d="M208.733 235.204C245.16 244.917 272.089 230.817 274.573 228.332C274.573 228.332 252.068 238.741 212.995 219.558C218.654 212.353 235.905 202.266 235.905 202.266C232.617 200.209 211.663 200.928 197.355 210.76C190.471 206.403 183.183 201.148 175.521 194.859L180.514 189.681C199.834 169.565 221.793 174.321 221.793 174.321C218 170.677 188.51 153.866 164.827 178.482L161.183 182.275C158.651 179.903 156.095 177.448 153.491 174.844L142.494 163.847L148.819 157.516C168.56 137.78 197.718 137.002 197.718 137.002C193.996 133.274 156.921 121.255 132.78 145.395L128.405 149.764L79.1141 100.461C75.3928 96.734 69.0561 97.0431 65.3408 100.758C61.6196 104.48 61.3164 110.81 65.0376 114.532L114.335 163.829L113.984 164.18C89.8438 188.326 99.8661 227.393 103.593 231.12C103.593 231.12 106.375 199.959 126.117 180.224L128.417 177.917L149.229 198.735C149.568 199.074 149.9 199.365 150.239 199.704C135.824 228.219 157.545 261.169 161.879 263.809C161.879 263.809 153.17 235.733 164.732 212.478C173.803 219.582 182.684 224.932 191.226 228.855C191.72 246.647 207.318 262.739 211.104 264.112C211.104 264.112 205.689 246.261 207.93 237.439L208.733 235.204Z"
|
||||
fill="#77B255"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
className="wind"
|
||||
d="M193.271 117.444C190.917 117.444 188.688 116.03 187.755 113.711C186.536 110.668 188.004 107.22 191.042 105.989C192.029 105.585 214.915 95.8958 217.293 69.3539C217.59 66.0904 220.408 63.7007 223.742 63.9623C227.018 64.2535 229.425 67.1425 229.134 70.412C226.09 104.361 196.731 116.511 195.482 117.01C194.757 117.308 194.008 117.444 193.271 117.444ZM222.993 141.222C220.532 141.222 218.232 139.683 217.382 137.233C216.306 134.142 217.929 130.766 221.008 129.678C222.125 129.274 247.907 119.507 252.989 81.005C253.417 77.7534 256.324 75.4469 259.659 75.8928C262.916 76.3208 265.205 79.3049 264.771 82.5624C258.702 128.632 226.322 140.42 224.949 140.895C224.301 141.115 223.641 141.222 222.993 141.222ZM240.833 176.889C238.734 176.889 236.707 175.771 235.619 173.804C234.038 170.933 235.09 167.318 237.961 165.737C238.122 165.648 254.315 156.606 259.367 144.825C260.663 141.805 264.171 140.414 267.167 141.704C270.186 142.994 271.583 146.489 270.287 149.509C263.612 165.083 244.494 175.712 243.686 176.158C242.776 176.651 241.802 176.889 240.833 176.889Z"
|
||||
fill="#5DADEC"
|
||||
/>
|
||||
</svg>
|
||||
</aside>
|
||||
</div>
|
||||
</FeatureBase>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
.start-feature {
|
||||
padding: 40px 0;
|
||||
display: flex;
|
||||
padding-bottom: 100px;
|
||||
|
||||
& > main {
|
||||
flex: 70%;
|
||||
}
|
||||
|
||||
& > aside {
|
||||
flex: 30%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import './Start.scss'
|
||||
import { Base } from '../FeatureComps/Base'
|
||||
import { StartCarousel } from './StartCarousel'
|
||||
import { StartCards } from './StartCards'
|
||||
|
||||
export const StartFeature: React.FC = () => {
|
||||
return (
|
||||
<Base className="start-feature">
|
||||
<aside>
|
||||
<StartCards />
|
||||
</aside>
|
||||
|
||||
<main>
|
||||
<StartCarousel />
|
||||
</main>
|
||||
</Base>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
.start-feature {
|
||||
@keyframes card-1-anim {
|
||||
from {
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(20deg) translateX(-20px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes card-2-anim {
|
||||
from {
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(-2deg) translateY(-10px) translateX(20px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes card-3-anim {
|
||||
from {
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(7deg) translateX(-50px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes card-4-anim {
|
||||
from {
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(-5deg) translateX(40px);
|
||||
}
|
||||
}
|
||||
|
||||
&.visible {
|
||||
.start-cards .card {
|
||||
&:nth-child(1) {
|
||||
animation: card-1-anim 0.8s ease-in-out forwards;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
animation: card-2-anim 0.8s ease-in-out forwards;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
animation: card-3-anim 0.8s ease-in-out forwards;
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
animation: card-4-anim 0.8s ease-in-out forwards;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.start-cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
|
||||
.card {
|
||||
background: var(--color-bg);
|
||||
box-shadow: 0px 0px 40px 0px rgba(0, 0, 0, 0.06);
|
||||
border: 1px solid var(--color-border-light);
|
||||
border-radius: 19px;
|
||||
padding: 20px 30px;
|
||||
width: 290px;
|
||||
height: 175px;
|
||||
font-size: 26px;
|
||||
|
||||
&:nth-child(1) {
|
||||
transform: rotate(10deg) translateY(-20px);
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
transform: rotate(-15deg) translateY(-10px) translateX(20px);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
z-index: 1;
|
||||
transform: rotate(15deg) translateY(-40px);
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
transform: rotate(2deg) translateX(-2px) translateY(20px);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: var(--color-border-light);
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
|
||||
&.loaded {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.username {
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
padding: 10px 0;
|
||||
|
||||
.text-row {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
background: rgba(217, 217, 217, 0.45);
|
||||
border-radius: 100px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&:nth-child(2) {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
margin-left: auto;
|
||||
padding: 5px 14px;
|
||||
height: 30px;
|
||||
width: 100px;
|
||||
border-radius: 8px;
|
||||
margin-top: 35px;
|
||||
margin-right: -15px;
|
||||
background: var(--color-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.text {
|
||||
border-radius: 100px;
|
||||
opacity: 0.3;
|
||||
background: var(--color-bg);
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import LazyImage from '../LazyImage'
|
||||
import './StartCards.scss'
|
||||
|
||||
interface ICard {
|
||||
username: string
|
||||
avatar: string
|
||||
text: string
|
||||
}
|
||||
|
||||
export const StartCards: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const [cards, setCards] = useState<ICard[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
setCards([
|
||||
{
|
||||
username: t('intro_stack_card_1_name'),
|
||||
avatar: '/images/avatar-1.webp',
|
||||
text: t('intro_stack_card_1_text'),
|
||||
},
|
||||
{
|
||||
username: t('intro_stack_card_2_name'),
|
||||
avatar: '/images/avatar-2.webp',
|
||||
text: t('intro_stack_card_2_text'),
|
||||
},
|
||||
{
|
||||
username: t('intro_stack_card_3_name'),
|
||||
avatar: '/images/avatar-3.webp',
|
||||
text: t('intro_stack_card_3_text'),
|
||||
},
|
||||
{
|
||||
username: '',
|
||||
avatar: '',
|
||||
text: '',
|
||||
},
|
||||
])
|
||||
}, [t, setCards])
|
||||
|
||||
return (
|
||||
<div className="start-cards">
|
||||
{cards.map((card, index) => (
|
||||
<div key={index} className="card">
|
||||
{card.username && (
|
||||
<div className="header">
|
||||
<div className="avatar">
|
||||
<LazyImage src={card.avatar} alt="avatar" referrerPolicy="no-referrer" />
|
||||
</div>
|
||||
<div className="username">{card.username}</div>
|
||||
</div>
|
||||
)}
|
||||
{card.text && <div className="content">{card.text}</div>}
|
||||
{!card.username && (
|
||||
<div className="placeholder">
|
||||
<div className="text-row"></div>
|
||||
<div className="text-row"></div>
|
||||
<div className="submit-button">
|
||||
<div className="text"></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
.start-carousel {
|
||||
$cardRadius: 24px;
|
||||
|
||||
.card {
|
||||
box-shadow: 0px 0px 40px 0px rgba(0, 0, 0, 0.06);
|
||||
border: 1px solid var(--color-border-light);
|
||||
background: var(--color-bg);
|
||||
}
|
||||
|
||||
.feature-cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.feature-item {
|
||||
color: var(--color-font-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: $cardRadius;
|
||||
padding: 40px 50px;
|
||||
cursor: pointer;
|
||||
transition: box-shadow 0.3s ease;
|
||||
|
||||
&.active {
|
||||
@extend .card;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.action .arrow-btn {
|
||||
transform: rotate(0);
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 70px;
|
||||
width: 70px;
|
||||
border-radius: 50%;
|
||||
background: #9fbef9;
|
||||
color: #133995;
|
||||
margin-right: 20px;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
|
||||
.title {
|
||||
font-size: 32px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin-top: 10px;
|
||||
font-size: 23px;
|
||||
color: var(--color-font-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.action {
|
||||
.arrow-btn {
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 50%;
|
||||
background: #ebf5fe;
|
||||
color: #007bff;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.progress-indicator {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.dots {
|
||||
background: var(--color-bg-grey-deep);
|
||||
padding: 10px 15px;
|
||||
border-radius: 100px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
||||
.dot {
|
||||
overflow: hidden;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 100px;
|
||||
background: var(--color-border);
|
||||
transition:
|
||||
width 0.8s ease,
|
||||
transform 0.3s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
filter: opacity(0.8);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.progress {
|
||||
transition: all 0.2s ease;
|
||||
height: 100%;
|
||||
background: var(--color-font-secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.screenshot-group {
|
||||
@extend .card;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
box-shadow: 0px 0px 40px 0px rgba(0, 0, 0, 0.06);
|
||||
gap: 16px;
|
||||
padding: 20px;
|
||||
border-radius: $cardRadius;
|
||||
|
||||
.screenshot {
|
||||
flex: 1;
|
||||
min-height: 320px;
|
||||
|
||||
img {
|
||||
border-radius: 11px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FiArrowUpRight } from 'react-icons/fi'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import LazyImage from '../LazyImage'
|
||||
import { getFeatures } from '../../features'
|
||||
import './StartCarousel.scss'
|
||||
|
||||
interface IFeature {
|
||||
name: string
|
||||
desc: string
|
||||
icon: React.FC
|
||||
link: string
|
||||
active: boolean
|
||||
bgColor: string
|
||||
textColor: string
|
||||
}
|
||||
|
||||
const allScreenshots = [
|
||||
'/images/screenshots/1.webp',
|
||||
'/images/screenshots/2.webp',
|
||||
'/images/screenshots/3.webp',
|
||||
'/images/screenshots/4.webp',
|
||||
'/images/screenshots/5.webp',
|
||||
'/images/screenshots/6.webp',
|
||||
'/images/screenshots/7.webp',
|
||||
'/images/screenshots/8.webp',
|
||||
]
|
||||
|
||||
const colors = [
|
||||
// Teal
|
||||
{
|
||||
bgColor: '#B2DFDB',
|
||||
textColor: '#004D40',
|
||||
},
|
||||
// Blue
|
||||
{
|
||||
bgColor: '#BBDEFB',
|
||||
textColor: '#0D47A1',
|
||||
},
|
||||
// Green
|
||||
{
|
||||
bgColor: '#C8E6C9',
|
||||
textColor: '#388E3C',
|
||||
},
|
||||
// Cyan
|
||||
{
|
||||
bgColor: '#E0F7FA',
|
||||
textColor: '#006064',
|
||||
},
|
||||
// Purple
|
||||
{
|
||||
bgColor: '#D1C4E9',
|
||||
textColor: '#512DA8',
|
||||
},
|
||||
// Orange
|
||||
{
|
||||
bgColor: '#FFCCBC',
|
||||
textColor: '#E64A19',
|
||||
},
|
||||
// Red
|
||||
{
|
||||
bgColor: '#FFC1C1',
|
||||
textColor: '#C62828',
|
||||
},
|
||||
// Yellow
|
||||
{
|
||||
bgColor: '#FFF59D',
|
||||
textColor: '#F57F17',
|
||||
},
|
||||
// Pink
|
||||
{
|
||||
bgColor: '#F8BBD0',
|
||||
textColor: '#C2185B',
|
||||
},
|
||||
// Lime
|
||||
{
|
||||
bgColor: '#E6EE9C',
|
||||
textColor: '#827717',
|
||||
},
|
||||
]
|
||||
|
||||
export const StartCarousel = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [allFeatures, setAllFeatures] = useState<
|
||||
{
|
||||
icon: React.FC
|
||||
name: string
|
||||
desc: string
|
||||
link: string
|
||||
}[]
|
||||
>([])
|
||||
|
||||
useEffect(() => {
|
||||
setAllFeatures(getFeatures(t))
|
||||
}, [t])
|
||||
|
||||
const [currentFeatures, setCurrentFeatures] = useState<IFeature[]>([])
|
||||
const [currentScreenshots, setCurrentScreenshots] = useState<string[]>([])
|
||||
|
||||
const [dots, setDots] = useState<{ percent: number; active: boolean }[]>(
|
||||
new Array(12).fill(0).map(() => ({ percent: 0, active: false })),
|
||||
)
|
||||
const [currentIndex, setCurrentIndex] = useState(0)
|
||||
const [currentPercent, setCurrentPercent] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setDots((prevDots) =>
|
||||
prevDots.map((_, i) => ({
|
||||
percent: i === currentIndex ? currentPercent : 0,
|
||||
active: i === currentIndex,
|
||||
})),
|
||||
)
|
||||
|
||||
setCurrentPercent((p) => {
|
||||
if (p < 100) return p + 1
|
||||
setCurrentIndex((index) => (index + 1) % dots.length)
|
||||
return 0
|
||||
})
|
||||
}, 80)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [dots.length, currentIndex, currentPercent])
|
||||
|
||||
const perPage = 2
|
||||
|
||||
const refreshFeatures = useCallback(() => {
|
||||
const newFeatures: IFeature[] = allFeatures
|
||||
.slice(
|
||||
(currentIndex * perPage) % allFeatures.length,
|
||||
(currentIndex * perPage + 2) % allFeatures.length,
|
||||
)
|
||||
.map((feature, index) => ({
|
||||
...feature,
|
||||
active: false,
|
||||
bgColor: colors[(currentIndex * perPage + index) % colors.length].bgColor,
|
||||
textColor: colors[(currentIndex * perPage + index) % colors.length].textColor,
|
||||
}))
|
||||
|
||||
if (currentPercent < 50) {
|
||||
if (newFeatures[0]) newFeatures[0].active = true
|
||||
if (newFeatures[1]) newFeatures[1].active = false
|
||||
} else {
|
||||
if (newFeatures[0]) newFeatures[0].active = false
|
||||
if (newFeatures[1]) newFeatures[1].active = true
|
||||
}
|
||||
|
||||
setCurrentFeatures(newFeatures)
|
||||
}, [currentIndex, currentPercent, allFeatures])
|
||||
|
||||
useEffect(() => {
|
||||
const start = (currentIndex * perPage) % allScreenshots.length
|
||||
let end = (currentIndex * perPage + 2) % allScreenshots.length
|
||||
if (start > end) end = allScreenshots.length
|
||||
const newScreenshots = allScreenshots.slice(start, end)
|
||||
setCurrentScreenshots(newScreenshots)
|
||||
}, [currentIndex])
|
||||
|
||||
useEffect(() => {
|
||||
if (![0, 50].includes(currentPercent)) return
|
||||
refreshFeatures()
|
||||
}, [currentIndex, currentPercent, refreshFeatures])
|
||||
|
||||
useEffect(() => {
|
||||
refreshFeatures()
|
||||
}, [allFeatures, refreshFeatures])
|
||||
|
||||
const switchPage = useCallback(
|
||||
(index: number) => {
|
||||
setCurrentIndex(index)
|
||||
setCurrentPercent(0)
|
||||
},
|
||||
[setCurrentIndex, setCurrentPercent],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="start-carousel">
|
||||
<div className="feature-cards">
|
||||
{currentFeatures.map((item) => (
|
||||
<a
|
||||
className={['feature-item', item.active ? 'active' : ''].join(' ')}
|
||||
key={item.name}
|
||||
href={item.link}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<div className="icon" style={{ backgroundColor: item.bgColor, color: item.textColor }}>
|
||||
<item.icon />
|
||||
</div>
|
||||
<div className="main">
|
||||
<div className="title">{item.name}</div>
|
||||
<div className="desc">{item.desc}</div>
|
||||
</div>
|
||||
<div className="action">
|
||||
<div className="arrow-btn">
|
||||
<FiArrowUpRight size="2em" />
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="progress-indicator">
|
||||
<div className="dots">
|
||||
{dots.map((dot, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={['dot', dot.active ? 'active' : ''].join(' ')}
|
||||
onClick={() => switchPage(index)}
|
||||
>
|
||||
<div className="progress" style={{ width: `${dot.percent}%` }} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="screenshot-group">
|
||||
{currentScreenshots.map((screenshot) => (
|
||||
<div className="screenshot" key={screenshot}>
|
||||
<LazyImage src={screenshot} alt="screenshot" referrerPolicy="no-referrer" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
.app-footer {
|
||||
margin: 80px auto;
|
||||
padding-top: 50px;
|
||||
margin: 0 auto;
|
||||
margin-top: 80px;
|
||||
padding-bottom: 80px;
|
||||
padding-top: 100px;
|
||||
text-align: center;
|
||||
border-top: 1px solid var(--color-border);
|
||||
font-size: 1.5rem;
|
||||
font-size: 20px;
|
||||
|
||||
.brand {
|
||||
color: var(--color-primary);
|
||||
color: var(--color-font);
|
||||
font-weight: 700;
|
||||
font-size: 1.5em;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.copyright {
|
||||
@@ -19,3 +20,47 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer-see-more-link {
|
||||
border-top: 1px solid var(--color-border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 100px;
|
||||
padding-top: 80px;
|
||||
|
||||
.link {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--color-font);
|
||||
background: var(--color-bg-grey-deep);
|
||||
padding: 12px;
|
||||
border-radius: 100px;
|
||||
font-size: 18px;
|
||||
|
||||
.text {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
height: 35px;
|
||||
width: 35px;
|
||||
background-color: var(--color-primary);
|
||||
color: #fff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.star-proposal {
|
||||
margin-top: 25px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
line-height: 2;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,41 @@
|
||||
import React from 'react'
|
||||
import './Footer.scss'
|
||||
import { RiArrowRightSLine } from 'react-icons/ri'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export const Footer: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="app-footer container">
|
||||
<div className="brand">Artalk</div>
|
||||
<div className="copyright">
|
||||
The Artalk. Made with <span className="red">♥️</span>.
|
||||
<>
|
||||
<div className="footer-see-more-link container">
|
||||
<a
|
||||
className="link"
|
||||
href="https://github.com/ArtalkJS/Artalk"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<span className="text">{t('see_more_on_github')}</span>
|
||||
<span className="icon">
|
||||
<RiArrowRightSLine />
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<span className="star-proposal">
|
||||
{t('star_proposal_line_1')}
|
||||
<br />
|
||||
{t('star_proposal_line_2')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="app-grey-bg app-footer-wrap">
|
||||
<div className="app-footer container">
|
||||
<div className="brand">Artalk</div>
|
||||
<div className="copyright">
|
||||
The Artalk. Made with <span className="red">♥️</span>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
.app-header {
|
||||
z-index: 999;
|
||||
position: sticky;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transition: background 0.2s ease;
|
||||
@@ -14,6 +15,7 @@
|
||||
}
|
||||
|
||||
&.fixed {
|
||||
backdrop-filter: saturate(1.8) blur(20px);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
background: var(--color-bg-transparent);
|
||||
}
|
||||
|
||||