feat: add payment gateway and limit features (#797)

* feat: add payment gateway and limit features

* fix: prettier

* fix: build frontend

* fix: remove unused module

* fix: onboarding

* fix: unused deps

* fix: prettier

* fix: tests

* fix: linter

* fix: env

---------

Co-authored-by: orig <oriorigranot@gamil.com>
This commit is contained in:
orig
2024-06-08 16:34:23 +03:00
committed by GitHub
parent 9db102470f
commit 671ac4c25b
95 changed files with 3624 additions and 1553 deletions

View File

@ -8,11 +8,17 @@ NODE_ENV=development
RATE_LIMIT_TTL=60
RATE_LIMIT_COUNT=100
# PADDLE - (Payment Gateway)
PADDLE_ENABLE=false
PADDLE_WEBHOOK_KEY=
PADDLE_SECRET_KEY=
# LOGGER
LOGGER_CONSOLE_THRESHOLD=INFO # DEBUG, INFO, WARN, ERROR, FATAL
# FRONTEND
DOMAIN=localhost
PUBLIC_PADDLE_KEY= # Not needed for local development
CLIENTSIDE_API_DOMAIN=http://localhost:3000 # Use this verible, while making client side API calls
API_DOMAIN=http://localhost:3000 # If you are running with docker compose change this to http://backend:3000
STORAGE_DOMAIN=Get it from https://cloud.digitalocean.com/spaces

View File

@ -40,4 +40,4 @@ jobs:
- name: Build & Push Affected Images to Registry
id: deploy-images-to-registry
run: |
npx nx affected -t "push-image-to-registry" --repository=${{ github.repository }} --github-sha="$GITHUB_SHA" --apiDomain=${{ secrets.API_DOMAIN }} --clientSideApiDomain=${{ secrets.CLIENTSIDE_API_DOMAIN }} --domain=${{ secrets.DOMAIN }} --storageDomain=${{ secrets.STORAGE_DOMAIN }}
npx nx affected -t "push-image-to-registry" --repository=${{ github.repository }} --github-sha="$GITHUB_SHA" --apiDomain=${{ secrets.API_DOMAIN }} --clientSideApiDomain=${{ secrets.CLIENTSIDE_API_DOMAIN }} --domain=${{ secrets.DOMAIN }} --storageDomain=${{ secrets.STORAGE_DOMAIN }} --publicPaddleKey=${{ secrets.PUBLIC_PADDLE_KEY }}

2
.vscode/launch.json vendored
View File

@ -8,7 +8,7 @@
"runtimeExecutable": "npx",
"runtimeArgs": ["nx", "serve", "backend"],
"console": "integratedTerminal",
"cwd": "${workspaceFolder}"
"cwd": "${workspaceFolder}/apps/backend"
},
{
"name": "Debug tracker",

View File

@ -176,6 +176,12 @@ For the minimal configuration you can just rename the `.example.env` files to `.
- **RATE_LIMIT_TTL**: Rate limit TTL (time to live)
- **RATE_LIMIT_COUNT**: Number of requests within the ttl
###### Paddle - (Payment Gateway - https://www.paddle.com/ - Optional)
- **PADDLE_ENABLE**: Wethter to enable Paddle or not
- **PADDLE_WEBHOOK_KEY**: Get it from your Paddle account
- **PADDLE_SECRET_KEY**: Get it from your Paddle account
###### Logger
- **LOGGER_CONSOLE_THRESHOLD**: Threshold level of the console transporter.
@ -183,6 +189,7 @@ For the minimal configuration you can just rename the `.example.env` files to `.
###### Frontend
- **DOMAIN**: Domain of your frontend app
- **PUBLIC_PADDLE_KEY**: Get it from your Paddle account (Not needed when running locally)
- **API_DOMAIN**: Domain of your backend instance (used for server side requests)
- **CLIENTSIDE_API_DOMAIN**: Domain of your backend instance (used for client side requests)
- **STORAGE_DOMAIN**=Domain of your bucket (used for storing images)
@ -211,6 +218,25 @@ For the minimal configuration you can just rename the `.example.env` files to `.
Happy Hacking !
### Change my plan on development
If you want to change your plan on developemnt (Assuming you have a local instance of PostgreSQL running on port 5432 and you don't have Paddle configured):
1. Register locally on the app.
2. Go to your database and create a new row in the `Subscription` table:
- `id`: 1
- `userId`: (you can find your user id in the `User` table)
- `plan`: (FREE / PRO / BUSINESS)
- `status`: active
- `endDate`: Choose a date in the future
- `scheduledToBeCancelled`: false
- `endDate`: empty (NULL)
- `nextBilledAt`: empty (NULL)
- `createdAt`: current date
- `updatedAt`: current date
3. Relogin to the app (refresh the JWT token)
4. You can now access the premium features.
<p align="right">(<a href="#readme-top">back to top</a>)</p>
<!-- USAGE EXAMPLES -->

View File

@ -4,8 +4,9 @@ import { UserCtx } from '../shared/decorators';
import { JwtAuthGuard } from '../auth/guards/jwt.guard';
import { UserContext } from '../auth/interfaces/user-context';
import { PrismaService } from '@reduced.to/prisma';
import { RestrictDays } from './analytics.guard';
@UseGuards(JwtAuthGuard)
@UseGuards(JwtAuthGuard, RestrictDays)
@Controller('analytics')
export class AnalyticsController {
constructor(private readonly analyticsService: AnalyticsService, private readonly prismaService: PrismaService) {}

View File

@ -0,0 +1,29 @@
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
import { UserContext } from '../auth/interfaces/user-context';
import { PLAN_LEVELS } from '@reduced.to/subscription-manager';
@Injectable()
export class RestrictDays implements CanActivate {
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
const user = request.user as UserContext;
const plan = user.plan || 'FREE';
const planConfig = PLAN_LEVELS[plan];
const maxDays = planConfig.FEATURES.ANALYTICS.value;
const query = request.query;
// Apply logic to modify the 'days' query parameter if it exists
if (query.days) {
const days = parseInt(query.days, 10);
if (isNaN(days) || days < 0) {
query.days = '0'; // Default to 0 if the value is invalid
} else if (days > maxDays) {
query.days = maxDays.toString();
}
}
// Allow the request to proceed
return true;
}
}

View File

@ -15,6 +15,8 @@ import { LinksModule } from './core/links/links.module';
import { ReportsModule } from './core/reports/reports.module';
import { MetadataModule } from './metadata/metadata.module';
import { AnalyticsModule } from './analytics/analytics.module';
import { BillingModule } from './billing/billing.module';
import { TasksModule } from './tasks/tasks.module';
@Module({
imports: [
@ -33,8 +35,10 @@ import { AnalyticsModule } from './analytics/analytics.module';
UsersModule,
LinksModule,
ReportsModule,
BillingModule,
MetadataModule,
AnalyticsModule,
TasksModule, // Should be imported only once to avoid multiple instances
],
providers: [
PrismaService,

View File

@ -2,7 +2,7 @@ import { Body, Controller, Get, Post, Req, Res, UnauthorizedException, UseGuards
import { Request, Response } from 'express';
import { AppConfigService } from '@reduced.to/config';
import { NovuService } from '../novu/novu.service';
import { PrismaService } from '@reduced.to/prisma';
import { Plan, PrismaService, Subscription } from '@reduced.to/prisma';
import { AuthService } from './auth.service';
import { SignupDto } from './dto/signup.dto';
import { JwtRefreshAuthGuard } from './guards/jwt-refresh.guard';
@ -57,7 +57,19 @@ export class AuthController {
@UseGuards(JwtRefreshAuthGuard)
@Post('/refresh')
async refresh(@Req() req: Request, @Res() res: Response) {
const tokens = await this.authService.refreshTokens(req.user as UserContext);
const userContext = req.user as UserContext;
let subscription: Subscription;
try {
subscription = await this.prismaService.subscription.findFirst({
where: {
userId: userContext.id,
},
});
} catch (e) {
console.error(e);
}
const newContext = { ...userContext, plan: subscription?.plan || Plan.FREE };
const tokens = await this.authService.refreshTokens(newContext);
res = setAuthCookies(res, this.appConfigService.getConfig().front.domain, tokens);
res.send(tokens);

View File

@ -16,6 +16,7 @@ import { ProvidersController } from './providers/providers.controller';
import { UsersModule } from '../core/users/users.module';
import { StorageModule } from '../storage/storage.module';
import { StorageService } from '../storage/storage.service';
import { BillingModule } from '../billing/billing.module';
@Module({
imports: [
@ -30,6 +31,7 @@ import { StorageService } from '../storage/storage.service';
}),
NovuModule,
StorageModule,
forwardRef(() => BillingModule),
forwardRef(() => UsersModule),
],
controllers: [AuthController, ProvidersController],

View File

@ -8,6 +8,9 @@ import { PrismaService } from '@reduced.to/prisma';
import { AuthService } from './auth.service';
import { SignupDto } from './dto/signup.dto';
import { StorageService } from '../storage/storage.service';
import { BillingModule } from '../billing/billing.module';
import { AppLoggerModule } from '@reduced.to/logger';
import { BillingService } from '../billing/billing.service';
describe('AuthService', () => {
let authService: AuthService;
@ -17,7 +20,7 @@ describe('AuthService', () => {
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [AppConfigModule],
imports: [AppConfigModule, AppLoggerModule],
providers: [
AuthService,
JwtService,
@ -31,6 +34,10 @@ describe('AuthService', () => {
},
},
},
{
provide: BillingService,
useValue: jest.fn(),
},
{
provide: StorageService,
useValue: jest.fn(),

View File

@ -6,6 +6,7 @@ import * as bcrypt from 'bcryptjs';
import { SignupDto } from './dto/signup.dto';
import { UserContext } from './interfaces/user-context';
import { PROFILE_PICTURE_PREFIX, StorageService } from '../storage/storage.service';
import { BillingService } from '../billing/billing.service';
@Injectable()
export class AuthService {
@ -13,7 +14,8 @@ export class AuthService {
private readonly prisma: PrismaService,
private readonly jwtService: JwtService,
private readonly storageService: StorageService,
private readonly appConfigService: AppConfigService
private readonly appConfigService: AppConfigService,
private readonly billingService: BillingService
) {}
async validateUser(email: string, password: string) {
@ -21,6 +23,9 @@ export class AuthService {
where: {
email,
},
include: {
subscription: true,
},
});
if (!user) {
return null;
@ -28,8 +33,11 @@ export class AuthService {
const verified = await bcrypt.compare(password, user.password);
if (verified) {
const { password, ...result } = user;
return result;
const { ...result } = user;
return {
...result,
plan: user.subscription?.plan || 'FREE',
};
}
return null;
@ -83,7 +91,15 @@ export class AuthService {
});
}
return this.prisma.user.create(createOptions);
createOptions.data['usage'] = {
create: {
linksCount: 0,
clicksCount: 0,
},
};
const createdUser = await this.prisma.user.create(createOptions);
return createdUser;
}
async verify(user: UserContext): Promise<{ verified: boolean }> {
@ -172,6 +188,7 @@ export class AuthService {
email: user.email,
name: user.name,
role: user.role,
plan: user.plan || 'FREE',
verified: user.verified,
iss: 'reduced.to',
};
@ -182,12 +199,32 @@ export class AuthService {
});
}
async delete(user: UserContext) {
async delete(userCtx: UserContext) {
const user = await this.prisma.user.findUnique({
where: {
id: userCtx.id,
},
include: {
subscription: true,
},
});
if (!user) {
throw new Error('User not found');
}
const promises: any[] = [this.storageService.delete(`${PROFILE_PICTURE_PREFIX}/${user.id}`)];
if (user.subscription) {
promises.push(this.billingService.cancelSubscription(user.id));
}
try {
await this.storageService.delete(`${PROFILE_PICTURE_PREFIX}/${user.id}`);
await Promise.all(promises);
} catch (error) {
// Ignore error
}
return this.prisma.user.delete({
where: {
id: user.id,

View File

@ -1,10 +1,12 @@
import { Role } from '@reduced.to/prisma';
import { PLAN_LEVELS } from '@reduced.to/subscription-manager';
export interface UserContext {
id: string;
email: string;
name: string;
role: Role;
plan?: keyof typeof PLAN_LEVELS;
refreshToken?: string;
verificationToken?: string;
verified: boolean;

View File

@ -33,7 +33,7 @@ export class ProvidersController {
throw new BadRequestException('User is not exists in request');
}
let user = await this.usersService.findByEmail(req.user.email);
let user = await this.usersService.findUserContextByEmail(req.user.email);
if (!user) {
user = await this.authService.signup({

View File

@ -17,6 +17,7 @@ export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
scope: ['email', 'profile'],
});
}
async validate(accessToken: string, refreshToken: string, profile: any, done: VerifyCallback): Promise<any> {
const { name, emails, photos, id } = profile;
@ -34,6 +35,7 @@ export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
refreshToken,
providerId: id,
};
done(null, user);
}
}

View File

@ -29,6 +29,6 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
if (!payload) {
throw new UnauthorizedException();
}
return { id: payload.id, email: payload.email, role: payload.role, verified: payload.verified };
return { id: payload.id, email: payload.email, role: payload.role, verified: payload.verified, plan: payload.plan };
}
}

View File

@ -17,6 +17,6 @@ export class VerifyStrategy extends PassportStrategy(Strategy, 'verify') {
if (!payload) {
throw new UnauthorizedException();
}
return { name: payload.name, email: payload.email };
return { name: payload.name, email: payload.email, plan: payload.plan };
}
}

View File

@ -0,0 +1,118 @@
import {
Controller,
Delete,
Get,
InternalServerErrorException,
NotFoundException,
Patch,
Post,
RawBodyRequest,
Req,
UseGuards,
} from '@nestjs/common';
import { BillingService } from './billing.service';
import { JwtAuthGuard } from '../auth/guards/jwt.guard';
import { UserContext } from '../auth/interfaces/user-context';
import { UserCtx } from '../shared/decorators';
import { AppConfigService } from '@reduced.to/config';
import { Body, Headers } from '@nestjs/common';
import { EventName, SubscriptionActivatedEvent, SubscriptionCanceledEvent, SubscriptionUpdatedEvent } from '@paddle/paddle-node-sdk';
import { Request } from 'express';
import { UpdatePlanDto } from './dto/update-plan.dto';
import { AppLoggerService } from '@reduced.to/logger';
import { AuthService } from '../auth/auth.service';
@Controller('billing')
export class BillingController {
private webhookSecret?: string;
constructor(
private readonly billingService: BillingService,
private readonly configService: AppConfigService,
private readonly loggerService: AppLoggerService,
private readonly authService: AuthService
) {
const { paddle } = this.configService.getConfig();
this.webhookSecret = paddle.webhookSecret;
}
@UseGuards(JwtAuthGuard)
@Get('/info')
async getBillingInfo(@UserCtx() user: UserContext) {
try {
return await this.billingService.getBillingInfo(user.id);
} catch (err: unknown) {
this.loggerService.error(`Could not fetch billing info for user ${user.id}`, err);
throw new InternalServerErrorException('Could not fetch billing info');
}
}
@UseGuards(JwtAuthGuard)
@Delete('/plan')
async cancelPlan(@UserCtx() user: UserContext) {
try {
await this.billingService.cancelSubscription(user.id);
return { message: 'Subscription cancelled' };
} catch (err: unknown) {
this.loggerService.error(`Could not cancel subscription for user ${user.id}`, err);
throw new InternalServerErrorException('Could not cancel subscription');
}
}
@UseGuards(JwtAuthGuard)
@Patch('/plan/resume')
async revertCancellation(@UserCtx() user: UserContext) {
try {
await this.billingService.resumeSubscription(user.id);
return { message: 'Subscription resumed' };
} catch (err: unknown) {
this.loggerService.error(`Could not resume subscription for user ${user.id}`, err);
throw new InternalServerErrorException('Could not resume subscription');
}
}
@UseGuards(JwtAuthGuard)
@Post('/plan')
async updatePlan(@UserCtx() user: UserContext, @Body() updatePlanDto: UpdatePlanDto) {
const { planId, itemId, operationType } = updatePlanDto;
try {
const updatedUser = await this.billingService.updateSubscription(user.id, planId, itemId, operationType);
const tokens = await this.authService.generateTokens(updatedUser);
return {
message: 'Plan updated',
...tokens,
};
} catch (err: unknown) {
this.loggerService.error(`Could not update subscription for user ${user.id} with planId: ${planId}, priceId: ${itemId}`, err);
throw new InternalServerErrorException('Could not update subscription');
}
}
@Post('/paddle/webhook')
async handlePaddleWebhook(@Headers('paddle-signature') signature: string, @Req() req: RawBodyRequest<Request>) {
if (!this.webhookSecret) {
throw new NotFoundException();
}
const responseText = req.rawBody.toString();
const parsedEvent = this.billingService.verifyWebhookSignature(signature, this.webhookSecret, responseText);
if (!parsedEvent) {
throw new NotFoundException();
}
switch (parsedEvent.eventType) {
case EventName.SubscriptionActivated:
await this.billingService.onSubscriptionAcvivated(parsedEvent as SubscriptionActivatedEvent);
break;
case EventName.SubscriptionCanceled:
await this.billingService.onSubscriptionCancelled(parsedEvent as SubscriptionCanceledEvent);
break;
case EventName.SubscriptionUpdated:
await this.billingService.onSubscriptionModified(parsedEvent as SubscriptionUpdatedEvent);
break;
default:
break;
}
}
}

View File

@ -0,0 +1,14 @@
import { Module, forwardRef } from '@nestjs/common';
import { PrismaModule } from '@reduced.to/prisma';
import { BillingController } from './billing.controller';
import { BillingService } from './billing.service';
import { AuthModule } from '../auth/auth.module';
import { UsageModule } from '@reduced.to/subscription-manager';
@Module({
imports: [forwardRef(() => AuthModule), PrismaModule, UsageModule],
controllers: [BillingController],
providers: [BillingService],
exports: [BillingService],
})
export class BillingModule {}

View File

@ -0,0 +1,355 @@
import { Injectable, OnModuleInit } from '@nestjs/common';
import { Plan, PrismaService } from '@reduced.to/prisma';
import { PLAN_LEVELS, UsageService } from '@reduced.to/subscription-manager';
import {
Environment,
Paddle,
SubscriptionActivatedEvent,
SubscriptionCanceledEvent,
SubscriptionUpdatedEvent,
} from '@paddle/paddle-node-sdk';
import { AppConfigService } from '@reduced.to/config';
import { AppLoggerService } from '@reduced.to/logger';
@Injectable()
export class BillingService implements OnModuleInit {
private paddleClient?: Paddle;
private enabled?: boolean;
constructor(
private readonly prisma: PrismaService,
private readonly appConfigService: AppConfigService,
private readonly logger: AppLoggerService,
private readonly usageService: UsageService
) {
const { paddle, general } = this.appConfigService.getConfig();
this.enabled = paddle.enable;
if (!this.enabled) {
return;
}
this.paddleClient = new Paddle(paddle.secret, {
environment: general.env === 'development' ? Environment.sandbox : Environment.production,
});
}
async onModuleInit() {
if (!this.enabled) {
return;
}
// Validate paddle plans
const ids = Object.keys(PLAN_LEVELS)
.map((plan) => PLAN_LEVELS[plan].PADDLE_PLAN_ID)
.filter((id) => id !== undefined);
try {
for (const id of ids) {
await this.paddleClient.products.get(id);
}
} catch (e) {
throw new Error('Invalid paddle configuration.');
}
}
verifyWebhookSignature(signature: string, secret: string, payload: string) {
if (!this.paddleClient) {
return;
}
return this.paddleClient.webhooks.unmarshal(payload, secret, signature);
}
async cancelSubscription(userId: string) {
const user = await this.prisma.user.findUnique({
where: { id: userId },
include: {
subscription: true,
},
});
if (!user) {
throw new Error('User not found');
}
if (!user.subscription) {
throw new Error('No subscription found');
}
const result = await this.paddleClient.subscriptions.cancel(user.subscription.id, {
effectiveFrom: 'next_billing_period',
});
const endDate = result.currentBillingPeriod.endsAt;
//Status will be updated in the webhook on cancel
try {
await this.prisma.subscription.update({
where: {
id: user.subscription.id,
},
data: {
endDate,
scheduledToBeCancelled: true,
},
});
} catch (e) {
throw new Error('Failed to cancel subscription');
}
}
async resumeSubscription(userId: string) {
const user = await this.prisma.user.findUnique({
where: { id: userId },
include: {
subscription: true,
},
});
if (!user) {
throw new Error('User not found');
}
if (!user.subscription) {
throw new Error('No subscription found');
}
if (!user.subscription.scheduledToBeCancelled) {
throw new Error('Subscription is not scheduled to be cancelled');
}
const result = await this.paddleClient.subscriptions.update(user.subscription.id, {
scheduledChange: null,
});
await this.prisma.subscription.update({
where: {
id: user.subscription.id,
},
data: {
endDate: null,
scheduledToBeCancelled: false,
status: result.status,
},
});
}
async updateSubscription(userId: string, newProductId: string, newPriceId: string, operationType: string) {
const user = await this.prisma.user.findUnique({
where: {
id: userId,
},
include: {
subscription: true,
},
});
if (!user) {
throw new Error('User not found');
}
if (!user.subscription) {
throw new Error('No subscription found');
}
const currentPaddleProductId = PLAN_LEVELS[user.subscription.plan].PADDLE_PLAN_ID;
if (!currentPaddleProductId) {
throw new Error('Invalid plan');
}
if (currentPaddleProductId === newProductId) {
throw new Error('Same plan selected');
}
const newPlan = Object.keys(PLAN_LEVELS).find((plan) => PLAN_LEVELS[plan].PADDLE_PLAN_ID === newProductId);
if (!newPlan) {
throw new Error('Invalid plan id');
}
const subscriptionUpdate = await this.paddleClient.subscriptions.update(user.subscription.id, {
items: [{ priceId: newPriceId, quantity: 1 }],
prorationBillingMode: 'prorated_immediately',
});
const newSub = await this.prisma.subscription.update({
where: {
id: user.subscription.id,
},
data: {
id: subscriptionUpdate.id,
plan: Plan[newPlan],
startDate: subscriptionUpdate.startedAt,
nextBilledAt: subscriptionUpdate.nextBilledAt,
status: subscriptionUpdate.status,
},
});
return { ...user, subscription: newSub };
}
async onSubscriptionModified(subData: SubscriptionUpdatedEvent) {
const { id } = subData.data;
const user = await this.extractUserFromWebhookData(subData.data);
if (!user) {
return;
}
const mainItem = subData.data.items[0];
const paddleProductId = mainItem.price.productId;
let plan = Object.keys(PLAN_LEVELS).find((plan) => PLAN_LEVELS[plan].PADDLE_PLAN_ID === paddleProductId) as Plan;
if (subData.data.status === 'canceled') {
plan = Plan.FREE;
await this.prisma.subscription.delete({
where: {
id: id,
},
});
} else {
await this.prisma.subscription.update({
where: {
id: id,
},
data: {
status: subData.data.status,
plan,
...(subData.data.nextBilledAt && { nextBilledAt: new Date(subData.data.nextBilledAt) }),
startDate: new Date(subData.data.startedAt),
},
});
}
await this.usageService.updateLimits(user.id, plan);
}
async onSubscriptionCancelled(subData: SubscriptionCanceledEvent) {
const { id } = subData.data;
const user = await this.extractUserFromWebhookData(subData.data);
if (!user) {
return;
}
if (user.subscription) {
await this.prisma.subscription.delete({
where: {
id,
},
});
}
await this.usageService.updateLimits(user.id, Plan.FREE);
}
async onSubscriptionAcvivated(subData: SubscriptionActivatedEvent) {
const user = await this.extractUserFromWebhookData(subData.data);
if (!user) {
return;
}
const mainItem = subData.data.items[0];
const paddleProductId = mainItem.price.productId;
const PLAN_KEY = Object.keys(PLAN_LEVELS).find((plan) => PLAN_LEVELS[plan].PADDLE_PLAN_ID === paddleProductId);
if (!PLAN_KEY) {
this.logger.error('Plan not found with id ', paddleProductId, 'subscription =>', subData.data.id);
}
const newSubsription = {
id: subData.data.id,
userId: user.id,
plan: PLAN_KEY as Plan,
startDate: new Date(subData.occurredAt),
nextBilledAt: new Date(subData.data.nextBilledAt),
status: subData.data.status,
};
try {
await this.prisma.subscription.upsert({
where: {
id: user.subscription?.id || subData.data.id,
},
update: {
id: newSubsription.id,
plan: newSubsription.plan,
startDate: newSubsription.startDate,
nextBilledAt: newSubsription.nextBilledAt,
status: newSubsription.status,
},
create: {
...newSubsription,
},
});
await this.usageService.updateLimits(user.id, PLAN_KEY);
} catch (e) {
this.logger.error('Failed to create subscription ', e);
}
}
async getBillingInfo(userId: string) {
const user = await this.prisma.user.findUnique({
where: { id: userId },
include: {
subscription: true,
usage: true,
},
});
if (!user) {
throw new Error('User not found');
}
const subscription = user.subscription;
const plan = subscription ? subscription.plan : Plan.FREE;
const planConfig = PLAN_LEVELS[plan];
const startDate = subscription ? subscription.startDate : user.createdAt;
const endDate = subscription ? subscription.endDate : null;
const nextBillingAt = subscription ? subscription.nextBilledAt : null;
const usage = {
currentLinkCount: user.usage?.linksCount || 0,
currentTrackedClicks: user.usage?.clicksCount || 0,
};
const limits = {
linksCount: planConfig.FEATURES.LINKS_COUNT.value,
trackedClicks: planConfig.FEATURES.TRACKED_CLICKS.value,
};
return {
id: subscription?.id || '',
plan,
startDate,
endDate,
scheduledToBeCancelled: subscription?.scheduledToBeCancelled || false,
nextBillingAt,
limits,
usage,
};
}
private async extractUserIdFromWebhookData(data: any) {
const { userId } = data.customData as { userId?: string };
return userId;
}
private async extractUserFromWebhookData(data: any) {
const userId = await this.extractUserIdFromWebhookData(data);
if (!userId) {
return;
}
return this.prisma.user.findUnique({
where: {
id: userId,
},
include: {
subscription: true,
},
});
}
}

View File

@ -0,0 +1,18 @@
import { IsDefined, IsString, MaxLength } from 'class-validator';
export class UpdatePlanDto {
@IsString()
@IsDefined()
@MaxLength(40)
itemId: string;
@IsString()
@IsDefined()
@MaxLength(40)
planId: string;
@IsString()
@IsDefined()
@MaxLength(40)
operationType: 'upgrade' | 'downgrade';
}

View File

@ -34,17 +34,28 @@ export class UsersService extends EntityService<User> {
return this.prismaService.user.count({ where });
}
async findByEmail(email: string): Promise<UserContext> {
async findUserContextByEmail(email: string): Promise<UserContext> {
const user = await this.prismaService.user.findUnique({
where: {
email,
},
include: {
subscription: true,
},
});
// We want to return undefined if user is not found
if (!user) {
return undefined;
}
delete user?.password;
delete user?.refreshToken;
return user;
return {
...user,
plan: user?.subscription?.plan || 'FREE',
};
}
async updateById(id: string, data: Prisma.UserUpdateInput): Promise<User> {

View File

@ -9,7 +9,7 @@ import cookieParser from 'cookie-parser';
import bodyParser from 'body-parser';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
const app = await NestFactory.create<NestExpressApplication>(AppModule, { rawBody: true });
app.enableVersioning({
type: VersioningType.URI,
@ -18,8 +18,8 @@ async function bootstrap() {
});
app.use(cookieParser());
app.use(bodyParser.json({ limit: '5mb' }));
app.use(bodyParser.urlencoded({ limit: '5mb', extended: true }));
app.useBodyParser('json', { limit: '5mb' });
app.useBodyParser('urlencoded', { limit: '5mb', extended: true });
app.enableCors({ origin: true, credentials: true });
app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));

View File

@ -0,0 +1,33 @@
import { UserContext } from '../../auth/interfaces/user-context';
import { createParamDecorator, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { Role } from '@reduced.to/prisma';
import { FEATURES, PLAN_LEVELS } from '@reduced.to/subscription-manager';
export const GuardFields = createParamDecorator((_, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user as UserContext;
const planName = user.plan || 'FREE';
const plan = PLAN_LEVELS[planName];
// Admins can do whatever the fuck they want
if (user.role === Role.ADMIN) {
return request.body;
}
const disabledFeatures = Object.keys(plan.FEATURES).filter((feature) => !plan.FEATURES[feature].enabled);
for (const field in request.body) {
const isNotPermitted = disabledFeatures.find((df) => {
const apiGuard = FEATURES[df].apiGuard;
if (!apiGuard) {
return false;
}
return RegExp(apiGuard).test(field);
});
if (isNotPermitted) {
throw new UnauthorizedException(`This feature is not available for your plan.`);
}
}
return request.body;
});

View File

@ -10,6 +10,7 @@ import { ShortenerProducer } from './producer/shortener.producer';
import { QueueManagerModule, QueueManagerService } from '@reduced.to/queue-manager';
import { IClientDetails } from '../shared/decorators/client-details/client-details.decorator';
import { SafeUrlService } from '@reduced.to/safe-url';
import { UsageService } from '@reduced.to/subscription-manager';
describe('ShortenerController', () => {
let shortenerController: ShortenerController;
@ -38,6 +39,12 @@ describe('ShortenerController', () => {
isSafeUrl: jest.fn().mockResolvedValue(true),
},
},
{
provide: UsageService,
useValue: {
isEligibleToCreateLink: jest.fn().mockResolvedValue(true),
},
},
QueueManagerService,
ShortenerProducer,
],

View File

@ -12,6 +12,8 @@ import { AppConfigService } from '@reduced.to/config';
import { Link } from '@prisma/client';
import { addUtmParams } from '@reduced.to/utils';
import { JwtAuthGuard } from '../auth/guards/jwt.guard';
import { UsageService } from '@reduced.to/subscription-manager';
import { GuardFields } from './guards/feature.guard';
interface LinkResponse extends Partial<Link> {
url: string;
@ -28,7 +30,8 @@ export class ShortenerController {
private readonly logger: AppLoggerService,
private readonly shortenerService: ShortenerService,
private readonly shortenerProducer: ShortenerProducer,
private readonly safeUrlService: SafeUrlService
private readonly safeUrlService: SafeUrlService,
private readonly usageService: UsageService
) {}
@UseGuards(JwtAuthGuard)
@ -73,7 +76,7 @@ export class ShortenerController {
@UseGuards(OptionalJwtAuthGuard)
@Post()
async shortener(@Body() shortenerDto: ShortenerDto, @Req() req: Request): Promise<{ key: string }> {
async shortener(@GuardFields() @Body() shortenerDto: ShortenerDto, @Req() req: Request): Promise<{ key: string }> {
const user = req.user as UserContext;
// Check if the url is safe
@ -97,14 +100,19 @@ export class ShortenerController {
return this.shortenerService.createShortenedUrl(rest);
}
// Only verified users can create shortened urls
if (!user?.verified) {
throw new BadRequestException('You must be verified in to create a shortened url');
}
// Hash the password if it exists in the request
if (shortenerDto.password) {
shortenerDto.password = await this.shortenerService.hashPassword(shortenerDto.password);
}
// Only verified users can create shortened urls
if (!user?.verified) {
throw new BadRequestException('You must be verified in to create a shortened url');
const isEligibleToCreateLink = await this.usageService.isEligibleToCreateLink(user.id);
if (!isEligibleToCreateLink) {
throw new BadRequestException('You have reached your link creation limit');
}
this.logger.log(`User ${user.id} is creating a shortened url for ${shortenerDto.url}`);

View File

@ -5,8 +5,9 @@ import { PrismaModule } from '@reduced.to/prisma';
import { ShortenerProducer } from './producer/shortener.producer';
import { QueueManagerModule, QueueManagerService } from '@reduced.to/queue-manager';
import { SafeUrlModule } from '@reduced.to/safe-url';
import { UsageModule } from '@reduced.to/subscription-manager';
@Module({
imports: [PrismaModule, QueueManagerModule, SafeUrlModule.forRootAsync()],
imports: [PrismaModule, QueueManagerModule, SafeUrlModule.forRootAsync(), UsageModule],
controllers: [ShortenerController],
providers: [ShortenerService, QueueManagerService, ShortenerProducer],
exports: [ShortenerService],

View File

@ -8,6 +8,7 @@ import { PrismaService } from '@reduced.to/prisma';
import { ShortenerDto } from './dto';
import { BadRequestException } from '@nestjs/common';
import { UserContext } from '../auth/interfaces/user-context';
import { UsageService } from '@reduced.to/subscription-manager';
const FIXED_SYSTEM_TIME = '1999-01-01T00:00:00Z';
@ -52,6 +53,13 @@ describe('ShortenerService', () => {
},
}),
},
{
provide: UsageService,
useValue: {
isEligibleToCreateLink: jest.fn().mockResolvedValue(true),
incrementLinksCount: jest.fn(),
},
},
],
}).compile();

View File

@ -7,13 +7,15 @@ import { UserContext } from '../auth/interfaces/user-context';
import { Link } from '@reduced.to/prisma';
import * as argon2 from 'argon2';
import { createUtmObject } from '@reduced.to/utils';
import { UsageService } from '@reduced.to/subscription-manager';
@Injectable()
export class ShortenerService {
constructor(
private readonly appCacheService: AppCacheService,
private readonly prisma: PrismaService,
private readonly appConfigService: AppConfigService
private readonly appConfigService: AppConfigService,
private readonly usageService: UsageService
) {}
/**
@ -127,8 +129,8 @@ export class ShortenerService {
if (password && shortenerDto.temporary) {
throw new BadRequestException('Temporary links cannot be password protected');
}
return this.prisma.link.create({ data });
const [_, createdLink] = await Promise.all([this.usageService.incrementLinksCount(user.id), this.prisma.link.create({ data })]);
return createdLink;
};
/**

View File

@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { UsageModule } from '@reduced.to/subscription-manager';
import { TasksService } from './tasks.service';
@Module({
imports: [UsageModule],
providers: [TasksService],
exports: [TasksService],
})
export class TasksModule {}

View File

@ -0,0 +1,15 @@
import { Injectable } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { UsageService } from '@reduced.to/subscription-manager';
@Injectable()
export class TasksService {
constructor(private readonly usageService: UsageService) {}
@Cron('0 0 1 * *') // Runs at midnight on the first day of every month
async handleCron() {
await this.usageService.runOnAllActiveUsers(async (id) => {
await this.usageService.resetUsage(id);
});
}
}

View File

@ -8,5 +8,5 @@
"target": "es2021"
},
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"],
"include": ["src/**/*.ts"]
"include": ["src/**/*.ts", "../../libs/subscription-manager/src/lib/usage/usage.service.ts"]
}

View File

@ -16,6 +16,7 @@ ARG API_DOMAIN
ARG CLIENTSIDE_API_DOMAIN
ARG DOMAIN
ARG STORAGE_DOMAIN
ARG PUBLIC_PADDLE_KEY
# Set environment variables
ENV NODE_ENV=production

View File

@ -74,7 +74,7 @@
"executor": "nx:run-commands",
"options": {
"commands": [
"docker build -f apps/frontend/Dockerfile . -t frontend --build-arg DOMAIN={args.domain} --build-arg API_DOMAIN={args.apiDomain} --build-arg CLIENTSIDE_API_DOMAIN={args.clientSideApiDomain} --build-arg STORAGE_DOMAIN={args.storageDomain}",
"docker build -f apps/frontend/Dockerfile . -t frontend --build-arg DOMAIN={args.domain} --build-arg API_DOMAIN={args.apiDomain} --build-arg CLIENTSIDE_API_DOMAIN={args.clientSideApiDomain} --build-arg STORAGE_DOMAIN={args.storageDomain} --build-arg PUBLIC_PADDLE_KEY={args.publicPaddleKey}",
"docker image tag frontend ghcr.io/{args.repository}/frontend:master",
"docker push ghcr.io/{args.repository}/frontend:master"
],

View File

@ -1,346 +0,0 @@
<svg width="1454" height="630" viewBox="0 35 1454 630" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3_341)">
<mask id="mask0_3_341" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="1455" height="630">
<path d="M1454.5 0H0.5V630H1454.5V0Z" fill="url(#paint0_linear_3_341)"/>
</mask>
<g mask="url(#mask0_3_341)">
<path d="M52.5343 0.868067C53.4639 0.331006 54.6097 0.331006 55.5393 0.868067L94.5662 23.4149C95.4942 23.9511 96.0657 24.941 96.0657 26.0121V71.1129C96.0657 72.184 95.4942 73.1739 94.5662 73.7101L55.5393 96.2569C54.6097 96.794 53.4639 96.794 52.5343 96.2569L13.5074 73.7101C12.5794 73.1739 12.0079 72.184 12.0079 71.1129V26.0121C12.0079 24.941 12.5794 23.9511 13.5074 23.4149L52.5343 0.868067Z" fill="#0F172A"/>
<path d="M55.289 1.30094L94.3158 23.8478C95.089 24.2946 95.5654 25.1195 95.5654 26.0121V71.1129C95.5654 72.0055 95.089 72.8304 94.3158 73.2772L55.289 95.8241C54.5142 96.2716 53.5594 96.2716 52.7846 95.8241L13.7578 73.2772C12.9845 72.8304 12.5082 72.0055 12.5082 71.1129V26.0121C12.5082 25.1195 12.9845 24.2946 13.7578 23.8478L52.7846 1.30094C53.5594 0.853383 54.5142 0.853383 55.289 1.30094Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M135.591 0.868067C136.521 0.331006 137.667 0.331006 138.597 0.868067L177.624 23.4149C178.551 23.9511 179.123 24.941 179.123 26.0121V71.1129C179.123 72.184 178.551 73.1739 177.624 73.7101L138.597 96.2569C137.667 96.794 136.521 96.794 135.591 96.2569L96.5646 73.7101C95.6365 73.1739 95.065 72.184 95.065 71.1129V26.0121C95.065 24.941 95.6365 23.9511 96.5646 23.4149L135.591 0.868067Z" fill="#0F172A"/>
<path d="M138.346 1.30094L177.373 23.8478C178.146 24.2946 178.623 25.1195 178.623 26.0121V71.1129C178.623 72.0055 178.146 72.8304 177.373 73.2772L138.346 95.8241C137.571 96.2716 136.617 96.2716 135.842 95.8241L96.8149 73.2772C96.0416 72.8304 95.5654 72.0055 95.5654 71.1129V26.0121C95.5654 25.1195 96.0416 24.2946 96.8149 23.8478L135.842 1.30094C136.617 0.853383 137.571 0.853383 138.346 1.30094Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M218.648 0.868067C219.578 0.331006 220.724 0.331006 221.654 0.868067L260.681 23.4149C261.609 23.9511 262.18 24.941 262.18 26.0121V71.1129C262.18 72.184 261.609 73.1739 260.681 73.7101L221.654 96.2569C220.724 96.794 219.578 96.794 218.648 96.2569L179.621 73.7101C178.694 73.1739 178.122 72.184 178.122 71.1129V26.0121C178.122 24.941 178.694 23.9511 179.621 23.4149L218.648 0.868067Z" fill="#0F172A"/>
<path d="M221.403 1.30094L260.43 23.8478C261.203 24.2946 261.68 25.1195 261.68 26.0121V71.1129C261.68 72.0055 261.203 72.8304 260.43 73.2772L221.403 95.8241C220.628 96.2716 219.674 96.2716 218.899 95.8241L179.872 73.2772C179.099 72.8304 178.622 72.0055 178.622 71.1129V26.0121C178.622 25.1195 179.099 24.2946 179.872 23.8478L218.899 1.30094C219.674 0.853383 220.628 0.853383 221.403 1.30094Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M301.705 0.868067C302.635 0.331006 303.782 0.331006 304.711 0.868067L343.738 23.4149C344.666 23.9511 345.237 24.941 345.237 26.0121V71.1129C345.237 72.184 344.666 73.1739 343.738 73.7101L304.711 96.2569C303.782 96.794 302.635 96.794 301.705 96.2569L262.678 73.7101C261.751 73.1739 261.179 72.184 261.179 71.1129V26.0121C261.179 24.941 261.751 23.9511 262.678 23.4149L301.705 0.868067Z" fill="#0F172A"/>
<path d="M304.46 1.30094L343.487 23.8478C344.26 24.2946 344.737 25.1195 344.737 26.0121V71.1129C344.737 72.0055 344.26 72.8304 343.487 73.2772L304.46 95.8241C303.686 96.2716 302.731 96.2716 301.956 95.8241L262.929 73.2772C262.156 72.8304 261.68 72.0055 261.68 71.1129V26.0121C261.68 25.1195 262.156 24.2946 262.929 23.8478L301.956 1.30094C302.731 0.853383 303.686 0.853383 304.46 1.30094Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M384.762 0.868067C385.692 0.331006 386.839 0.331006 387.768 0.868067L426.795 23.4149C427.723 23.9511 428.294 24.941 428.294 26.0121V71.1129C428.294 72.184 427.723 73.1739 426.795 73.7101L387.768 96.2569C386.839 96.794 385.692 96.794 384.762 96.2569L345.735 73.7101C344.808 73.1739 344.236 72.184 344.236 71.1129V26.0121C344.236 24.941 344.808 23.9511 345.735 23.4149L384.762 0.868067Z" fill="#0F172A"/>
<path d="M387.517 1.30094L426.544 23.8478C427.318 24.2946 427.794 25.1195 427.794 26.0121V71.1129C427.794 72.0055 427.318 72.8304 426.544 73.2772L387.517 95.8241C386.743 96.2716 385.788 96.2716 385.013 95.8241L345.987 73.2772C345.213 72.8304 344.737 72.0055 344.737 71.1129V26.0121C344.737 25.1195 345.213 24.2946 345.987 23.8478L385.013 1.30094C385.788 0.853383 386.743 0.853383 387.517 1.30094Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M467.819 0.868067C468.749 0.331006 469.896 0.331006 470.825 0.868067L509.852 23.4149C510.78 23.9511 511.351 24.941 511.351 26.0121V71.1129C511.351 72.184 510.78 73.1739 509.852 73.7101L470.825 96.2569C469.896 96.794 468.749 96.794 467.819 96.2569L428.793 73.7101C427.865 73.1739 427.294 72.184 427.294 71.1129V26.0121C427.294 24.941 427.865 23.9511 428.793 23.4149L467.819 0.868067Z" fill="#0F172A"/>
<path d="M470.574 1.30094L509.601 23.8478C510.375 24.2946 510.851 25.1195 510.851 26.0121V71.1129C510.851 72.0055 510.375 72.8304 509.601 73.2772L470.574 95.8241C469.8 96.2716 468.845 96.2716 468.071 95.8241L429.044 73.2772C428.27 72.8304 427.794 72.0055 427.794 71.1129V26.0121C427.794 25.1195 428.27 24.2946 429.044 23.8478L468.071 1.30094C468.845 0.853383 469.8 0.853383 470.574 1.30094Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M550.877 0.868067C551.806 0.331006 552.953 0.331006 553.883 0.868067L592.909 23.4149C593.837 23.9511 594.408 24.941 594.408 26.0121V71.1129C594.408 72.184 593.837 73.1739 592.909 73.7101L553.883 96.2569C552.953 96.794 551.806 96.794 550.877 96.2569L511.85 73.7101C510.922 73.1739 510.351 72.184 510.351 71.1129V26.0121C510.351 24.941 510.922 23.9511 511.85 23.4149L550.877 0.868067Z" fill="#0F172A"/>
<path d="M553.631 1.30094L592.658 23.8478C593.432 24.2946 593.908 25.1195 593.908 26.0121V71.1129C593.908 72.0055 593.432 72.8304 592.658 73.2772L553.631 95.8241C552.857 96.2716 551.902 96.2716 551.128 95.8241L512.101 73.2772C511.327 72.8304 510.851 72.0055 510.851 71.1129V26.0121C510.851 25.1195 511.327 24.2946 512.101 23.8478L551.128 1.30094C551.902 0.853383 552.857 0.853383 553.631 1.30094Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M633.934 0.868067C634.863 0.331006 636.01 0.331006 636.94 0.868067L675.967 23.4149C676.894 23.9511 677.466 24.941 677.466 26.0121V71.1129C677.466 72.184 676.894 73.1739 675.967 73.7101L636.94 96.2569C636.01 96.794 634.863 96.794 633.934 96.2569L594.907 73.7101C593.979 73.1739 593.408 72.184 593.408 71.1129V26.0121C593.408 24.941 593.979 23.9511 594.907 23.4149L633.934 0.868067Z" fill="#0F172A"/>
<path d="M636.689 1.30094L675.715 23.8478C676.489 24.2946 676.965 25.1195 676.965 26.0121V71.1129C676.965 72.0055 676.489 72.8304 675.715 73.2772L636.689 95.8241C635.914 96.2716 634.959 96.2716 634.185 95.8241L595.158 73.2772C594.384 72.8304 593.908 72.0055 593.908 71.1129V26.0121C593.908 25.1195 594.384 24.2946 595.158 23.8478L634.185 1.30094C634.959 0.853383 635.914 0.853383 636.689 1.30094Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M716.991 0.868067C717.92 0.331006 719.067 0.331006 719.997 0.868067L759.024 23.4149C759.951 23.9511 760.523 24.941 760.523 26.0121V71.1129C760.523 72.184 759.951 73.1739 759.024 73.7101L719.997 96.2569C719.067 96.794 717.92 96.794 716.991 96.2569L677.964 73.7101C677.036 73.1739 676.465 72.184 676.465 71.1129V26.0121C676.465 24.941 677.036 23.9511 677.964 23.4149L716.991 0.868067Z" fill="#0F172A"/>
<path d="M719.746 1.30094L758.772 23.8478C759.546 24.2946 760.022 25.1195 760.022 26.0121V71.1129C760.022 72.0055 759.546 72.8304 758.772 73.2772L719.746 95.8241C718.971 96.2716 718.016 96.2716 717.242 95.8241L678.215 73.2772C677.442 72.8304 676.965 72.0055 676.965 71.1129V26.0121C676.965 25.1195 677.442 24.2946 678.215 23.8478L717.242 1.30094C718.016 0.853383 718.971 0.853383 719.746 1.30094Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M800.048 0.868067C800.977 0.331006 802.124 0.331006 803.054 0.868067L842.081 23.4149C843.008 23.9511 843.58 24.941 843.58 26.0121V71.1129C843.58 72.184 843.008 73.1739 842.081 73.7101L803.054 96.2569C802.124 96.794 800.977 96.794 800.048 96.2569L761.021 73.7101C760.093 73.1739 759.522 72.184 759.522 71.1129V26.0121C759.522 24.941 760.093 23.9511 761.021 23.4149L800.048 0.868067Z" fill="#4C1D95" fill-opacity="0.12"/>
<path d="M802.803 1.30094L841.83 23.8478C842.603 24.2946 843.079 25.1195 843.079 26.0121V71.1129C843.079 72.0055 842.603 72.8304 841.83 73.2772L802.803 95.8241C802.028 96.2716 801.074 96.2716 800.299 95.8241L761.272 73.2772C760.499 72.8304 760.022 72.0055 760.022 71.1129V26.0121C760.022 25.1195 760.499 24.2946 761.272 23.8478L800.299 1.30094C801.074 0.853383 802.028 0.853383 802.803 1.30094Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M883.105 0.868067C884.035 0.331006 885.181 0.331006 886.111 0.868067L925.138 23.4149C926.066 23.9511 926.637 24.941 926.637 26.0121V71.1129C926.637 72.184 926.066 73.1739 925.138 73.7101L886.111 96.2569C885.181 96.794 884.035 96.794 883.105 96.2569L844.078 73.7101C843.15 73.1739 842.579 72.184 842.579 71.1129V26.0121C842.579 24.941 843.15 23.9511 844.078 23.4149L883.105 0.868067Z" fill="#0F172A"/>
<path d="M885.86 1.30094L924.887 23.8478C925.66 24.2946 926.137 25.1195 926.137 26.0121V71.1129C926.137 72.0055 925.66 72.8304 924.887 73.2772L885.86 95.8241C885.085 96.2716 884.131 96.2716 883.356 95.8241L844.329 73.2772C843.556 72.8304 843.079 72.0055 843.079 71.1129V26.0121C843.079 25.1195 843.556 24.2946 844.329 23.8478L883.356 1.30094C884.131 0.853383 885.085 0.853383 885.86 1.30094Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M966.162 0.868067C967.092 0.331006 968.239 0.331006 969.168 0.868067L1008.19 23.4149C1009.12 23.9511 1009.69 24.941 1009.69 26.0121V71.1129C1009.69 72.184 1009.12 73.1739 1008.19 73.7101L969.168 96.2569C968.239 96.794 967.092 96.794 966.162 96.2569L927.135 73.7101C926.208 73.1739 925.636 72.184 925.636 71.1129V26.0121C925.636 24.941 926.208 23.9511 927.135 23.4149L966.162 0.868067Z" fill="#0F172A"/>
<path d="M968.917 1.30094L1007.94 23.8478C1008.71 24.2946 1009.19 25.1195 1009.19 26.0121V71.1129C1009.19 72.0055 1008.71 72.8304 1007.94 73.2772L968.917 95.8241C968.142 96.2716 967.188 96.2716 966.413 95.8241L927.386 73.2772C926.613 72.8304 926.137 72.0055 926.137 71.1129V26.0121C926.137 25.1195 926.613 24.2946 927.386 23.8478L966.413 1.30094C967.188 0.853383 968.142 0.853383 968.917 1.30094Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1049.22 0.868067C1050.15 0.331006 1051.29 0.331006 1052.22 0.868067L1091.25 23.4149C1092.18 23.9511 1092.75 24.941 1092.75 26.0121V71.1129C1092.75 72.184 1092.18 73.1739 1091.25 73.7101L1052.22 96.2569C1051.29 96.794 1050.15 96.794 1049.22 96.2569L1010.19 73.7101C1009.26 73.1739 1008.69 72.184 1008.69 71.1129V26.0121C1008.69 24.941 1009.26 23.9511 1010.19 23.4149L1049.22 0.868067Z" fill="#0F172A"/>
<path d="M1051.97 1.30094L1091 23.8478C1091.77 24.2946 1092.25 25.1195 1092.25 26.0121V71.1129C1092.25 72.0055 1091.77 72.8304 1091 73.2772L1051.97 95.8241C1051.2 96.2716 1050.24 96.2716 1049.47 95.8241L1010.44 73.2772C1009.67 72.8304 1009.19 72.0055 1009.19 71.1129V26.0121C1009.19 25.1195 1009.67 24.2946 1010.44 23.8478L1049.47 1.30094C1050.24 0.853383 1051.2 0.853383 1051.97 1.30094Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1132.28 0.868067C1133.21 0.331006 1134.35 0.331006 1135.28 0.868067L1174.31 23.4149C1175.24 23.9511 1175.81 24.941 1175.81 26.0121V71.1129C1175.81 72.184 1175.24 73.1739 1174.31 73.7101L1135.28 96.2569C1134.35 96.794 1133.21 96.794 1132.28 96.2569L1093.25 73.7101C1092.32 73.1739 1091.75 72.184 1091.75 71.1129V26.0121C1091.75 24.941 1092.32 23.9511 1093.25 23.4149L1132.28 0.868067Z" fill="#0F172A"/>
<path d="M1135.03 1.30094L1174.06 23.8478C1174.83 24.2946 1175.31 25.1195 1175.31 26.0121V71.1129C1175.31 72.0055 1174.83 72.8304 1174.06 73.2772L1135.03 95.8241C1134.26 96.2716 1133.3 96.2716 1132.53 95.8241L1093.5 73.2772C1092.73 72.8304 1092.25 72.0055 1092.25 71.1129V26.0121C1092.25 25.1195 1092.73 24.2946 1093.5 23.8478L1132.53 1.30094C1133.3 0.853383 1134.26 0.853383 1135.03 1.30094Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1215.34 0.868067C1216.27 0.331006 1217.41 0.331006 1218.34 0.868067L1257.36 23.4149C1258.3 23.9511 1258.87 24.941 1258.87 26.0121V71.1129C1258.87 72.184 1258.3 73.1739 1257.36 73.7101L1218.34 96.2569C1217.41 96.794 1216.27 96.794 1215.34 96.2569L1176.31 73.7101C1175.38 73.1739 1174.81 72.184 1174.81 71.1129V26.0121C1174.81 24.941 1175.38 23.9511 1176.31 23.4149L1215.34 0.868067Z" fill="#0F172A"/>
<path d="M1218.09 1.30094L1257.11 23.8478C1257.88 24.2946 1258.37 25.1195 1258.37 26.0121V71.1129C1258.37 72.0055 1257.88 72.8304 1257.11 73.2772L1218.09 95.8241C1217.32 96.2716 1216.36 96.2716 1215.59 95.8241L1176.56 73.2772C1175.79 72.8304 1175.31 72.0055 1175.31 71.1129V26.0121C1175.31 25.1195 1175.79 24.2946 1176.56 23.8478L1215.59 1.30094C1216.36 0.853383 1217.32 0.853383 1218.09 1.30094Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1298.39 0.868067C1299.32 0.331006 1300.46 0.331006 1301.39 0.868067L1340.42 23.4149C1341.35 23.9511 1341.92 24.941 1341.92 26.0121V71.1129C1341.92 72.184 1341.35 73.1739 1340.42 73.7101L1301.39 96.2569C1300.46 96.794 1299.32 96.794 1298.39 96.2569L1259.37 73.7101C1258.44 73.1739 1257.86 72.184 1257.86 71.1129V26.0121C1257.86 24.941 1258.44 23.9511 1259.37 23.4149L1298.39 0.868067Z" fill="#0F172A"/>
<path d="M1301.14 1.30094L1340.17 23.8478C1340.94 24.2946 1341.42 25.1195 1341.42 26.0121V71.1129C1341.42 72.0055 1340.94 72.8304 1340.17 73.2772L1301.14 95.8241C1300.37 96.2716 1299.41 96.2716 1298.64 95.8241L1259.62 73.2772C1258.85 72.8304 1258.37 72.0055 1258.37 71.1129V26.0121C1258.37 25.1195 1258.85 24.2946 1259.62 23.8478L1298.64 1.30094C1299.41 0.853383 1300.37 0.853383 1301.14 1.30094Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1381.45 0.868067C1382.38 0.331006 1383.52 0.331006 1384.45 0.868067L1423.48 23.4149C1424.41 23.9511 1424.98 24.941 1424.98 26.0121V71.1129C1424.98 72.184 1424.41 73.1739 1423.48 73.7101L1384.45 96.2569C1383.52 96.794 1382.38 96.794 1381.45 96.2569L1342.42 73.7101C1341.49 73.1739 1340.92 72.184 1340.92 71.1129V26.0121C1340.92 24.941 1341.49 23.9511 1342.42 23.4149L1381.45 0.868067Z" fill="#0F172A"/>
<path d="M1384.2 1.30094L1423.23 23.8478C1424 24.2946 1424.48 25.1195 1424.48 26.0121V71.1129C1424.48 72.0055 1424 72.8304 1423.23 73.2772L1384.2 95.8241C1383.43 96.2716 1382.47 96.2716 1381.7 95.8241L1342.67 73.2772C1341.9 72.8304 1341.42 72.0055 1341.42 71.1129V26.0121C1341.42 25.1195 1341.9 24.2946 1342.67 23.8478L1381.7 1.30094C1382.47 0.853383 1383.43 0.853383 1384.2 1.30094Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1464.51 0.868067C1465.44 0.331006 1466.58 0.331006 1467.51 0.868067L1506.54 23.4149C1507.47 23.9511 1508.04 24.941 1508.04 26.0121V71.1129C1508.04 72.184 1507.47 73.1739 1506.54 73.7101L1467.51 96.2569C1466.58 96.794 1465.44 96.794 1464.51 96.2569L1425.48 73.7101C1424.55 73.1739 1423.98 72.184 1423.98 71.1129V26.0121C1423.98 24.941 1424.55 23.9511 1425.48 23.4149L1464.51 0.868067Z" fill="#0F172A"/>
<path d="M1467.26 1.30094L1506.29 23.8478C1507.06 24.2946 1507.54 25.1195 1507.54 26.0121V71.1129C1507.54 72.0055 1507.06 72.8304 1506.29 73.2772L1467.26 95.8241C1466.49 96.2716 1465.53 96.2716 1464.76 95.8241L1425.73 73.2772C1424.96 72.8304 1424.48 72.0055 1424.48 71.1129V26.0121C1424.48 25.1195 1424.96 24.2946 1425.73 23.8478L1464.76 1.30094C1465.53 0.853383 1466.49 0.853383 1467.26 1.30094Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M11.0057 71.9932C11.9354 71.4561 13.0812 71.4561 14.0108 71.9932L53.0376 94.54C53.9657 95.0762 54.5372 96.0661 54.5372 97.1372V142.238C54.5372 143.309 53.9657 144.299 53.0376 144.835L14.0108 167.382C13.0812 167.919 11.9354 167.919 11.0057 167.382L-28.0211 144.835C-28.9491 144.299 -29.5206 143.309 -29.5206 142.238V97.1372C-29.5206 96.0661 -28.9491 95.0762 -28.0211 94.54L11.0057 71.9932Z" fill="#0F172A"/>
<path d="M13.7604 72.426L52.7873 94.9729C53.5605 95.4197 54.0368 96.2446 54.0368 97.1372V142.238C54.0368 143.131 53.5605 143.955 52.7873 144.402L13.7604 166.949C12.9857 167.397 12.0308 167.397 11.2561 166.949L-27.7707 144.402C-28.5441 143.955 -29.0203 143.131 -29.0203 142.238V97.1372C-29.0203 96.2446 -28.5441 95.4197 -27.7707 94.9729L11.2561 72.426C12.0308 71.9785 12.9857 71.9785 13.7604 72.426Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M94.0628 71.9932C94.9925 71.4561 96.1383 71.4561 97.0679 71.9932L136.095 94.54C137.023 95.0762 137.594 96.0661 137.594 97.1372V142.238C137.594 143.309 137.023 144.299 136.095 144.835L97.0679 167.382C96.1383 167.919 94.9925 167.919 94.0628 167.382L55.036 144.835C54.108 144.299 53.5365 143.309 53.5365 142.238V97.1372C53.5365 96.0661 54.108 95.0762 55.036 94.54L94.0628 71.9932Z" fill="#0F172A"/>
<path d="M96.8175 72.426L135.844 94.9729C136.618 95.4197 137.094 96.2446 137.094 97.1372V142.238C137.094 143.131 136.618 143.955 135.844 144.402L96.8175 166.949C96.0428 167.397 95.0879 167.397 94.3132 166.949L55.2864 144.402C54.513 143.955 54.0368 143.131 54.0368 142.238V97.1372C54.0368 96.2446 54.513 95.4197 55.2864 94.9729L94.3132 72.426C95.0879 71.9785 96.0428 71.9785 96.8175 72.426Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M177.119 71.9932C178.049 71.4561 179.196 71.4561 180.126 71.9932L219.152 94.54C220.08 95.0762 220.651 96.0661 220.651 97.1372V142.238C220.651 143.309 220.08 144.299 219.152 144.835L180.126 167.382C179.196 167.919 178.049 167.919 177.119 167.382L138.093 144.835C137.165 144.299 136.594 143.309 136.594 142.238V97.1372C136.594 96.0661 137.165 95.0762 138.093 94.54L177.119 71.9932Z" fill="#0F172A"/>
<path d="M179.874 72.426L218.901 94.9729C219.675 95.4197 220.151 96.2446 220.151 97.1372V142.238C220.151 143.131 219.675 143.955 218.901 144.402L179.874 166.949C179.1 167.397 178.145 167.397 177.371 166.949L138.344 144.402C137.57 143.955 137.094 143.131 137.094 142.238V97.1372C137.094 96.2446 137.57 95.4197 138.344 94.9729L177.371 72.426C178.145 71.9785 179.1 71.9785 179.874 72.426Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M260.177 71.9932C261.106 71.4561 262.253 71.4561 263.183 71.9932L302.21 94.54C303.137 95.0762 303.709 96.0661 303.709 97.1372V142.238C303.709 143.309 303.137 144.299 302.21 144.835L263.183 167.382C262.253 167.919 261.106 167.919 260.177 167.382L221.15 144.835C220.222 144.299 219.651 143.309 219.651 142.238V97.1372C219.651 96.0661 220.222 95.0762 221.15 94.54L260.177 71.9932Z" fill="#0F172A"/>
<path d="M262.931 72.426L301.958 94.9729C302.732 95.4197 303.208 96.2446 303.208 97.1372V142.238C303.208 143.131 302.732 143.955 301.958 144.402L262.931 166.949C262.157 167.397 261.202 167.397 260.428 166.949L221.401 144.402C220.627 143.955 220.151 143.131 220.151 142.238V97.1372C220.151 96.2446 220.627 95.4197 221.401 94.9729L260.428 72.426C261.202 71.9785 262.157 71.9785 262.931 72.426Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M343.234 71.9932C344.163 71.4561 345.31 71.4561 346.24 71.9932L385.267 94.54C386.194 95.0762 386.766 96.0661 386.766 97.1372V142.238C386.766 143.309 386.194 144.299 385.267 144.835L346.24 167.382C345.31 167.919 344.163 167.919 343.234 167.382L304.207 144.835C303.279 144.299 302.708 143.309 302.708 142.238V97.1372C302.708 96.0661 303.279 95.0762 304.207 94.54L343.234 71.9932Z" fill="#0F172A"/>
<path d="M345.989 72.426L385.015 94.9729C385.789 95.4197 386.265 96.2446 386.265 97.1372V142.238C386.265 143.131 385.789 143.955 385.015 144.402L345.989 166.949C345.214 167.397 344.259 167.397 343.485 166.949L304.458 144.402C303.685 143.955 303.208 143.131 303.208 142.238V97.1372C303.208 96.2446 303.685 95.4197 304.458 94.9729L343.485 72.426C344.259 71.9785 345.214 71.9785 345.989 72.426Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M426.291 71.9932C427.22 71.4561 428.367 71.4561 429.297 71.9932L468.324 94.54C469.251 95.0762 469.823 96.0661 469.823 97.1372V142.238C469.823 143.309 469.251 144.299 468.324 144.835L429.297 167.382C428.367 167.919 427.22 167.919 426.291 167.382L387.264 144.835C386.336 144.299 385.765 143.309 385.765 142.238V97.1372C385.765 96.0661 386.336 95.0762 387.264 94.54L426.291 71.9932Z" fill="#0F172A"/>
<path d="M429.046 72.426L468.073 94.9729C468.846 95.4197 469.322 96.2446 469.322 97.1372V142.238C469.322 143.131 468.846 143.955 468.073 144.402L429.046 166.949C428.271 167.397 427.317 167.397 426.542 166.949L387.515 144.402C386.742 143.955 386.265 143.131 386.265 142.238V97.1372C386.265 96.2446 386.742 95.4197 387.515 94.9729L426.542 72.426C427.317 71.9785 428.271 71.9785 429.046 72.426Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M509.348 71.9932C510.278 71.4561 511.424 71.4561 512.354 71.9932L551.381 94.54C552.309 95.0762 552.88 96.0661 552.88 97.1372V142.238C552.88 143.309 552.309 144.299 551.381 144.835L512.354 167.382C511.424 167.919 510.278 167.919 509.348 167.382L470.321 144.835C469.393 144.299 468.822 143.309 468.822 142.238V97.1372C468.822 96.0661 469.393 95.0762 470.321 94.54L509.348 71.9932Z" fill="#0F172A"/>
<path d="M512.103 72.426L551.13 94.9729C551.903 95.4197 552.38 96.2446 552.38 97.1372V142.238C552.38 143.131 551.903 143.955 551.13 144.402L512.103 166.949C511.328 167.397 510.374 167.397 509.599 166.949L470.572 144.402C469.799 143.955 469.322 143.131 469.322 142.238V97.1372C469.322 96.2446 469.799 95.4197 470.572 94.9729L509.599 72.426C510.374 71.9785 511.328 71.9785 512.103 72.426Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M592.405 71.9932C593.335 71.4561 594.482 71.4561 595.411 71.9932L634.438 94.54C635.366 95.0762 635.937 96.0661 635.937 97.1372V142.238C635.937 143.309 635.366 144.299 634.438 144.835L595.411 167.382C594.482 167.919 593.335 167.919 592.405 167.382L553.378 144.835C552.451 144.299 551.879 143.309 551.879 142.238V97.1372C551.879 96.0661 552.451 95.0762 553.378 94.54L592.405 71.9932Z" fill="#0F172A"/>
<path d="M595.16 72.426L634.187 94.9729C634.96 95.4197 635.437 96.2446 635.437 97.1372V142.238C635.437 143.131 634.96 143.955 634.187 144.402L595.16 166.949C594.385 167.397 593.431 167.397 592.656 166.949L553.629 144.402C552.856 143.955 552.38 143.131 552.38 142.238V97.1372C552.38 96.2446 552.856 95.4197 553.629 94.9729L592.656 72.426C593.431 71.9785 594.385 71.9785 595.16 72.426Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M675.462 71.9932C676.392 71.4561 677.539 71.4561 678.468 71.9932L717.495 94.54C718.423 95.0762 718.994 96.0661 718.994 97.1372V142.238C718.994 143.309 718.423 144.299 717.495 144.835L678.468 167.382C677.539 167.919 676.392 167.919 675.462 167.382L636.435 144.835C635.508 144.299 634.936 143.309 634.936 142.238V97.1372C634.936 96.0661 635.508 95.0762 636.435 94.54L675.462 71.9932Z" fill="#0F172A"/>
<path d="M678.217 72.426L717.244 94.9729C718.017 95.4197 718.494 96.2446 718.494 97.1372V142.238C718.494 143.131 718.017 143.955 717.244 144.402L678.217 166.949C677.443 167.397 676.488 167.397 675.713 166.949L636.687 144.402C635.913 143.955 635.437 143.131 635.437 142.238V97.1372C635.437 96.2446 635.913 95.4197 636.687 94.9729L675.713 72.426C676.488 71.9785 677.443 71.9785 678.217 72.426Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M758.519 71.9932C759.449 71.4561 760.596 71.4561 761.525 71.9932L800.552 94.54C801.48 95.0762 802.051 96.0661 802.051 97.1372V142.238C802.051 143.309 801.48 144.299 800.552 144.835L761.525 167.382C760.596 167.919 759.449 167.919 758.519 167.382L719.492 144.835C718.565 144.299 717.993 143.309 717.993 142.238V97.1372C717.993 96.0661 718.565 95.0762 719.492 94.54L758.519 71.9932Z" fill="#0F172A"/>
<path d="M761.274 72.426L800.301 94.9729C801.075 95.4197 801.551 96.2446 801.551 97.1372V142.238C801.551 143.131 801.075 143.955 800.301 144.402L761.274 166.949C760.5 167.397 759.545 167.397 758.771 166.949L719.744 144.402C718.97 143.955 718.494 143.131 718.494 142.238V97.1372C718.494 96.2446 718.97 95.4197 719.744 94.9729L758.771 72.426C759.545 71.9785 760.5 71.9785 761.274 72.426Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M841.576 71.9932C842.506 71.4561 843.653 71.4561 844.583 71.9932L883.609 94.54C884.537 95.0762 885.108 96.0661 885.108 97.1372V142.238C885.108 143.309 884.537 144.299 883.609 144.835L844.583 167.382C843.653 167.919 842.506 167.919 841.576 167.382L802.55 144.835C801.622 144.299 801.051 143.309 801.051 142.238V97.1372C801.051 96.0661 801.622 95.0762 802.55 94.54L841.576 71.9932Z" fill="#0F172A"/>
<path d="M844.331 72.426L883.358 94.9729C884.132 95.4197 884.608 96.2446 884.608 97.1372V142.238C884.608 143.131 884.132 143.955 883.358 144.402L844.331 166.949C843.557 167.397 842.602 167.397 841.828 166.949L802.801 144.402C802.027 143.955 801.551 143.131 801.551 142.238V97.1372C801.551 96.2446 802.027 95.4197 802.801 94.9729L841.828 72.426C842.602 71.9785 843.557 71.9785 844.331 72.426Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M924.634 71.9932C925.563 71.4561 926.71 71.4561 927.64 71.9932L966.666 94.54C967.594 95.0762 968.165 96.0661 968.165 97.1372V142.238C968.165 143.309 967.594 144.299 966.666 144.835L927.64 167.382C926.71 167.919 925.563 167.919 924.634 167.382L885.607 144.835C884.679 144.299 884.108 143.309 884.108 142.238V97.1372C884.108 96.0661 884.679 95.0762 885.607 94.54L924.634 71.9932Z" fill="#0F172A"/>
<path d="M927.388 72.426L966.415 94.9729C967.189 95.4197 967.665 96.2446 967.665 97.1372V142.238C967.665 143.131 967.189 143.955 966.415 144.402L927.388 166.949C926.614 167.397 925.659 167.397 924.885 166.949L885.858 144.402C885.084 143.955 884.608 143.131 884.608 142.238V97.1372C884.608 96.2446 885.084 95.4197 885.858 94.9729L924.885 72.426C925.659 71.9785 926.614 71.9785 927.388 72.426Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1007.69 71.9932C1008.62 71.4561 1009.76 71.4561 1010.69 71.9932L1049.72 94.54C1050.65 95.0762 1051.22 96.0661 1051.22 97.1372V142.238C1051.22 143.309 1050.65 144.299 1049.72 144.835L1010.69 167.382C1009.76 167.919 1008.62 167.919 1007.69 167.382L968.664 144.835C967.736 144.299 967.165 143.309 967.165 142.238V97.1372C967.165 96.0661 967.736 95.0762 968.664 94.54L1007.69 71.9932Z" fill="#0F172A"/>
<path d="M1010.44 72.426L1049.47 94.9729C1050.24 95.4197 1050.72 96.2446 1050.72 97.1372V142.238C1050.72 143.131 1050.24 143.955 1049.47 144.402L1010.44 166.949C1009.67 167.397 1008.71 167.397 1007.94 166.949L968.915 144.402C968.142 143.955 967.665 143.131 967.665 142.238V97.1372C967.665 96.2446 968.142 95.4197 968.915 94.9729L1007.94 72.426C1008.71 71.9785 1009.67 71.9785 1010.44 72.426Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1090.75 71.9932C1091.68 71.4561 1092.82 71.4561 1093.75 71.9932L1132.78 94.54C1133.71 95.0762 1134.28 96.0661 1134.28 97.1372V142.238C1134.28 143.309 1133.71 144.299 1132.78 144.835L1093.75 167.382C1092.82 167.919 1091.68 167.919 1090.75 167.382L1051.72 144.835C1050.79 144.299 1050.22 143.309 1050.22 142.238V97.1372C1050.22 96.0661 1050.79 95.0762 1051.72 94.54L1090.75 71.9932Z" fill="#0F172A"/>
<path d="M1093.5 72.426L1132.53 94.9729C1133.3 95.4197 1133.78 96.2446 1133.78 97.1372V142.238C1133.78 143.131 1133.3 143.955 1132.53 144.402L1093.5 166.949C1092.73 167.397 1091.77 167.397 1091 166.949L1051.97 144.402C1051.2 143.955 1050.72 143.131 1050.72 142.238V97.1372C1050.72 96.2446 1051.2 95.4197 1051.97 94.9729L1091 72.426C1091.77 71.9785 1092.73 71.9785 1093.5 72.426Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1173.81 71.9932C1174.74 71.4561 1175.88 71.4561 1176.81 71.9932L1215.84 94.54C1216.77 95.0762 1217.34 96.0661 1217.34 97.1372V142.238C1217.34 143.309 1216.77 144.299 1215.84 144.835L1176.81 167.382C1175.88 167.919 1174.74 167.919 1173.81 167.382L1134.78 144.835C1133.85 144.299 1133.28 143.309 1133.28 142.238V97.1372C1133.28 96.0661 1133.85 95.0762 1134.78 94.54L1173.81 71.9932Z" fill="#0F172A"/>
<path d="M1176.56 72.426L1215.59 94.9729C1216.36 95.4197 1216.84 96.2446 1216.84 97.1372V142.238C1216.84 143.131 1216.36 143.955 1215.59 144.402L1176.56 166.949C1175.79 167.397 1174.83 167.397 1174.06 166.949L1135.03 144.402C1134.26 143.955 1133.78 143.131 1133.78 142.238V97.1372C1133.78 96.2446 1134.26 95.4197 1135.03 94.9729L1174.06 72.426C1174.83 71.9785 1175.79 71.9785 1176.56 72.426Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1256.86 71.9932C1257.79 71.4561 1258.94 71.4561 1259.87 71.9932L1298.89 94.54C1299.82 95.0762 1300.39 96.0661 1300.39 97.1372V142.238C1300.39 143.309 1299.82 144.299 1298.89 144.835L1259.87 167.382C1258.94 167.919 1257.79 167.919 1256.86 167.382L1217.84 144.835C1216.91 144.299 1216.34 143.309 1216.34 142.238V97.1372C1216.34 96.0661 1216.91 95.0762 1217.84 94.54L1256.86 71.9932Z" fill="#0F172A"/>
<path d="M1259.62 72.426L1298.64 94.9729C1299.41 95.4197 1299.89 96.2446 1299.89 97.1372V142.238C1299.89 143.131 1299.41 143.955 1298.64 144.402L1259.62 166.949C1258.85 167.397 1257.88 167.397 1257.11 166.949L1218.09 144.402C1217.32 143.955 1216.84 143.131 1216.84 142.238V97.1372C1216.84 96.2446 1217.32 95.4197 1218.09 94.9729L1257.11 72.426C1257.88 71.9785 1258.85 71.9785 1259.62 72.426Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1339.92 71.9932C1340.85 71.4561 1341.99 71.4561 1342.92 71.9932L1381.95 94.54C1382.88 95.0762 1383.45 96.0661 1383.45 97.1372V142.238C1383.45 143.309 1382.88 144.299 1381.95 144.835L1342.92 167.382C1341.99 167.919 1340.85 167.919 1339.92 167.382L1300.89 144.835C1299.96 144.299 1299.39 143.309 1299.39 142.238V97.1372C1299.39 96.0661 1299.96 95.0762 1300.89 94.54L1339.92 71.9932Z" fill="#0F172A"/>
<path d="M1342.67 72.426L1381.7 94.9729C1382.47 95.4197 1382.95 96.2446 1382.95 97.1372V142.238C1382.95 143.131 1382.47 143.955 1381.7 144.402L1342.67 166.949C1341.9 167.397 1340.94 167.397 1340.17 166.949L1301.14 144.402C1300.37 143.955 1299.89 143.131 1299.89 142.238V97.1372C1299.89 96.2446 1300.37 95.4197 1301.14 94.9729L1340.17 72.426C1340.94 71.9785 1341.9 71.9785 1342.67 72.426Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1422.98 71.9932C1423.91 71.4561 1425.05 71.4561 1425.98 71.9932L1465.01 94.54C1465.94 95.0762 1466.51 96.0661 1466.51 97.1372V142.238C1466.51 143.309 1465.94 144.299 1465.01 144.835L1425.98 167.382C1425.05 167.919 1423.91 167.919 1422.98 167.382L1383.95 144.835C1383.02 144.299 1382.45 143.309 1382.45 142.238V97.1372C1382.45 96.0661 1383.02 95.0762 1383.95 94.54L1422.98 71.9932Z" fill="#0F172A"/>
<path d="M1425.73 72.426L1464.76 94.9729C1465.53 95.4197 1466.01 96.2446 1466.01 97.1372V142.238C1466.01 143.131 1465.53 143.955 1464.76 144.402L1425.73 166.949C1424.96 167.397 1424 167.397 1423.23 166.949L1384.2 144.402C1383.43 143.955 1382.95 143.131 1382.95 142.238V97.1372C1382.95 96.2446 1383.43 95.4197 1384.2 94.9729L1423.23 72.426C1424 71.9785 1424.96 71.9785 1425.73 72.426Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M52.5343 143.118C53.4639 142.581 54.6097 142.581 55.5393 143.118L94.5662 165.665C95.4942 166.201 96.0657 167.191 96.0657 168.262V213.363C96.0657 214.434 95.4942 215.424 94.5662 215.96L55.5393 238.507C54.6097 239.044 53.4639 239.044 52.5343 238.507L13.5074 215.96C12.5794 215.424 12.0079 214.434 12.0079 213.363V168.262C12.0079 167.191 12.5794 166.201 13.5074 165.665L52.5343 143.118Z" fill="#0F172A"/>
<path d="M55.289 143.551L94.3158 166.098C95.089 166.545 95.5654 167.369 95.5654 168.262V213.363C95.5654 214.256 95.089 215.08 94.3158 215.527L55.289 238.074C54.5142 238.522 53.5594 238.522 52.7846 238.074L13.7578 215.527C12.9845 215.08 12.5082 214.256 12.5082 213.363V168.262C12.5082 167.369 12.9845 166.545 13.7578 166.098L52.7846 143.551C53.5594 143.103 54.5142 143.103 55.289 143.551Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M135.591 143.118C136.521 142.581 137.667 142.581 138.597 143.118L177.624 165.665C178.551 166.201 179.123 167.191 179.123 168.262V213.363C179.123 214.434 178.551 215.424 177.624 215.96L138.597 238.507C137.667 239.044 136.521 239.044 135.591 238.507L96.5646 215.96C95.6365 215.424 95.065 214.434 95.065 213.363V168.262C95.065 167.191 95.6365 166.201 96.5646 165.665L135.591 143.118Z" fill="#0F172A"/>
<path d="M138.346 143.551L177.373 166.098C178.146 166.545 178.623 167.369 178.623 168.262V213.363C178.623 214.256 178.146 215.08 177.373 215.527L138.346 238.074C137.571 238.522 136.617 238.522 135.842 238.074L96.8149 215.527C96.0416 215.08 95.5654 214.256 95.5654 213.363V168.262C95.5654 167.369 96.0416 166.545 96.8149 166.098L135.842 143.551C136.617 143.103 137.571 143.103 138.346 143.551Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M218.648 143.118C219.578 142.581 220.724 142.581 221.654 143.118L260.681 165.665C261.609 166.201 262.18 167.191 262.18 168.262V213.363C262.18 214.434 261.609 215.424 260.681 215.96L221.654 238.507C220.724 239.044 219.578 239.044 218.648 238.507L179.621 215.96C178.694 215.424 178.122 214.434 178.122 213.363V168.262C178.122 167.191 178.694 166.201 179.621 165.665L218.648 143.118Z" fill="#0F172A"/>
<path d="M221.403 143.551L260.43 166.098C261.203 166.545 261.68 167.369 261.68 168.262V213.363C261.68 214.256 261.203 215.08 260.43 215.527L221.403 238.074C220.628 238.522 219.674 238.522 218.899 238.074L179.872 215.527C179.099 215.08 178.622 214.256 178.622 213.363V168.262C178.622 167.369 179.099 166.545 179.872 166.098L218.899 143.551C219.674 143.103 220.628 143.103 221.403 143.551Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M301.705 143.118C302.635 142.581 303.782 142.581 304.711 143.118L343.738 165.665C344.666 166.201 345.237 167.191 345.237 168.262V213.363C345.237 214.434 344.666 215.424 343.738 215.96L304.711 238.507C303.782 239.044 302.635 239.044 301.705 238.507L262.678 215.96C261.751 215.424 261.179 214.434 261.179 213.363V168.262C261.179 167.191 261.751 166.201 262.678 165.665L301.705 143.118Z" fill="#0F172A"/>
<path d="M304.46 143.551L343.487 166.098C344.26 166.545 344.737 167.369 344.737 168.262V213.363C344.737 214.256 344.26 215.08 343.487 215.527L304.46 238.074C303.686 238.522 302.731 238.522 301.956 238.074L262.929 215.527C262.156 215.08 261.68 214.256 261.68 213.363V168.262C261.68 167.369 262.156 166.545 262.929 166.098L301.956 143.551C302.731 143.103 303.686 143.103 304.46 143.551Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M384.762 143.118C385.692 142.581 386.839 142.581 387.768 143.118L426.795 165.665C427.723 166.201 428.294 167.191 428.294 168.262V213.363C428.294 214.434 427.723 215.424 426.795 215.96L387.768 238.507C386.839 239.044 385.692 239.044 384.762 238.507L345.735 215.96C344.808 215.424 344.236 214.434 344.236 213.363V168.262C344.236 167.191 344.808 166.201 345.735 165.665L384.762 143.118Z" fill="#4C1D95" fill-opacity="0.12"/>
<path d="M387.517 143.551L426.544 166.098C427.318 166.545 427.794 167.369 427.794 168.262V213.363C427.794 214.256 427.318 215.08 426.544 215.527L387.517 238.074C386.743 238.522 385.788 238.522 385.013 238.074L345.987 215.527C345.213 215.08 344.737 214.256 344.737 213.363V168.262C344.737 167.369 345.213 166.545 345.987 166.098L385.013 143.551C385.788 143.103 386.743 143.103 387.517 143.551Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M467.819 143.118C468.749 142.581 469.896 142.581 470.825 143.118L509.852 165.665C510.78 166.201 511.351 167.191 511.351 168.262V213.363C511.351 214.434 510.78 215.424 509.852 215.96L470.825 238.507C469.896 239.044 468.749 239.044 467.819 238.507L428.793 215.96C427.865 215.424 427.294 214.434 427.294 213.363V168.262C427.294 167.191 427.865 166.201 428.793 165.665L467.819 143.118Z" fill="#0F172A"/>
<path d="M470.574 143.551L509.601 166.098C510.375 166.545 510.851 167.369 510.851 168.262V213.363C510.851 214.256 510.375 215.08 509.601 215.527L470.574 238.074C469.8 238.522 468.845 238.522 468.071 238.074L429.044 215.527C428.27 215.08 427.794 214.256 427.794 213.363V168.262C427.794 167.369 428.27 166.545 429.044 166.098L468.071 143.551C468.845 143.103 469.8 143.103 470.574 143.551Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M550.877 143.118C551.806 142.581 552.953 142.581 553.883 143.118L592.909 165.665C593.837 166.201 594.408 167.191 594.408 168.262V213.363C594.408 214.434 593.837 215.424 592.909 215.96L553.883 238.507C552.953 239.044 551.806 239.044 550.877 238.507L511.85 215.96C510.922 215.424 510.351 214.434 510.351 213.363V168.262C510.351 167.191 510.922 166.201 511.85 165.665L550.877 143.118Z" fill="#0F172A"/>
<path d="M553.631 143.551L592.658 166.098C593.432 166.545 593.908 167.369 593.908 168.262V213.363C593.908 214.256 593.432 215.08 592.658 215.527L553.631 238.074C552.857 238.522 551.902 238.522 551.128 238.074L512.101 215.527C511.327 215.08 510.851 214.256 510.851 213.363V168.262C510.851 167.369 511.327 166.545 512.101 166.098L551.128 143.551C551.902 143.103 552.857 143.103 553.631 143.551Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M633.934 143.118C634.863 142.581 636.01 142.581 636.94 143.118L675.967 165.665C676.894 166.201 677.466 167.191 677.466 168.262V213.363C677.466 214.434 676.894 215.424 675.967 215.96L636.94 238.507C636.01 239.044 634.863 239.044 633.934 238.507L594.907 215.96C593.979 215.424 593.408 214.434 593.408 213.363V168.262C593.408 167.191 593.979 166.201 594.907 165.665L633.934 143.118Z" fill="#0F172A"/>
<path d="M636.689 143.551L675.715 166.098C676.489 166.545 676.965 167.369 676.965 168.262V213.363C676.965 214.256 676.489 215.08 675.715 215.527L636.689 238.074C635.914 238.522 634.959 238.522 634.185 238.074L595.158 215.527C594.384 215.08 593.908 214.256 593.908 213.363V168.262C593.908 167.369 594.384 166.545 595.158 166.098L634.185 143.551C634.959 143.103 635.914 143.103 636.689 143.551Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M716.991 143.118C717.92 142.581 719.067 142.581 719.997 143.118L759.024 165.665C759.951 166.201 760.523 167.191 760.523 168.262V213.363C760.523 214.434 759.951 215.424 759.024 215.96L719.997 238.507C719.067 239.044 717.92 239.044 716.991 238.507L677.964 215.96C677.036 215.424 676.465 214.434 676.465 213.363V168.262C676.465 167.191 677.036 166.201 677.964 165.665L716.991 143.118Z" fill="#0F172A"/>
<path d="M719.746 143.551L758.772 166.098C759.546 166.545 760.022 167.369 760.022 168.262V213.363C760.022 214.256 759.546 215.08 758.772 215.527L719.746 238.074C718.971 238.522 718.016 238.522 717.242 238.074L678.215 215.527C677.442 215.08 676.965 214.256 676.965 213.363V168.262C676.965 167.369 677.442 166.545 678.215 166.098L717.242 143.551C718.016 143.103 718.971 143.103 719.746 143.551Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M800.048 143.118C800.977 142.581 802.124 142.581 803.054 143.118L842.081 165.665C843.008 166.201 843.58 167.191 843.58 168.262V213.363C843.58 214.434 843.008 215.424 842.081 215.96L803.054 238.507C802.124 239.044 800.977 239.044 800.048 238.507L761.021 215.96C760.093 215.424 759.522 214.434 759.522 213.363V168.262C759.522 167.191 760.093 166.201 761.021 165.665L800.048 143.118Z" fill="#0F172A"/>
<path d="M802.803 143.551L841.83 166.098C842.603 166.545 843.079 167.369 843.079 168.262V213.363C843.079 214.256 842.603 215.08 841.83 215.527L802.803 238.074C802.028 238.522 801.074 238.522 800.299 238.074L761.272 215.527C760.499 215.08 760.022 214.256 760.022 213.363V168.262C760.022 167.369 760.499 166.545 761.272 166.098L800.299 143.551C801.074 143.103 802.028 143.103 802.803 143.551Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M883.105 143.118C884.035 142.581 885.181 142.581 886.111 143.118L925.138 165.665C926.066 166.201 926.637 167.191 926.637 168.262V213.363C926.637 214.434 926.066 215.424 925.138 215.96L886.111 238.507C885.181 239.044 884.035 239.044 883.105 238.507L844.078 215.96C843.15 215.424 842.579 214.434 842.579 213.363V168.262C842.579 167.191 843.15 166.201 844.078 165.665L883.105 143.118Z" fill="#0F172A"/>
<path d="M885.86 143.551L924.887 166.098C925.66 166.545 926.137 167.369 926.137 168.262V213.363C926.137 214.256 925.66 215.08 924.887 215.527L885.86 238.074C885.085 238.522 884.131 238.522 883.356 238.074L844.329 215.527C843.556 215.08 843.079 214.256 843.079 213.363V168.262C843.079 167.369 843.556 166.545 844.329 166.098L883.356 143.551C884.131 143.103 885.085 143.103 885.86 143.551Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M966.162 143.118C967.092 142.581 968.239 142.581 969.168 143.118L1008.19 165.665C1009.12 166.201 1009.69 167.191 1009.69 168.262V213.363C1009.69 214.434 1009.12 215.424 1008.19 215.96L969.168 238.507C968.239 239.044 967.092 239.044 966.162 238.507L927.135 215.96C926.208 215.424 925.636 214.434 925.636 213.363V168.262C925.636 167.191 926.208 166.201 927.135 165.665L966.162 143.118Z" fill="#0F172A"/>
<path d="M968.917 143.551L1007.94 166.098C1008.71 166.545 1009.19 167.369 1009.19 168.262V213.363C1009.19 214.256 1008.71 215.08 1007.94 215.527L968.917 238.074C968.142 238.522 967.188 238.522 966.413 238.074L927.386 215.527C926.613 215.08 926.137 214.256 926.137 213.363V168.262C926.137 167.369 926.613 166.545 927.386 166.098L966.413 143.551C967.188 143.103 968.142 143.103 968.917 143.551Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1049.22 143.118C1050.15 142.581 1051.29 142.581 1052.22 143.118L1091.25 165.665C1092.18 166.201 1092.75 167.191 1092.75 168.262V213.363C1092.75 214.434 1092.18 215.424 1091.25 215.96L1052.22 238.507C1051.29 239.044 1050.15 239.044 1049.22 238.507L1010.19 215.96C1009.26 215.424 1008.69 214.434 1008.69 213.363V168.262C1008.69 167.191 1009.26 166.201 1010.19 165.665L1049.22 143.118Z" fill="#0F172A"/>
<path d="M1051.97 143.551L1091 166.098C1091.77 166.545 1092.25 167.369 1092.25 168.262V213.363C1092.25 214.256 1091.77 215.08 1091 215.527L1051.97 238.074C1051.2 238.522 1050.24 238.522 1049.47 238.074L1010.44 215.527C1009.67 215.08 1009.19 214.256 1009.19 213.363V168.262C1009.19 167.369 1009.67 166.545 1010.44 166.098L1049.47 143.551C1050.24 143.103 1051.2 143.103 1051.97 143.551Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1132.28 143.118C1133.21 142.581 1134.35 142.581 1135.28 143.118L1174.31 165.665C1175.24 166.201 1175.81 167.191 1175.81 168.262V213.363C1175.81 214.434 1175.24 215.424 1174.31 215.96L1135.28 238.507C1134.35 239.044 1133.21 239.044 1132.28 238.507L1093.25 215.96C1092.32 215.424 1091.75 214.434 1091.75 213.363V168.262C1091.75 167.191 1092.32 166.201 1093.25 165.665L1132.28 143.118Z" fill="#4C1D95" fill-opacity="0.12"/>
<path d="M1135.03 143.551L1174.06 166.098C1174.83 166.545 1175.31 167.369 1175.31 168.262V213.363C1175.31 214.256 1174.83 215.08 1174.06 215.527L1135.03 238.074C1134.26 238.522 1133.3 238.522 1132.53 238.074L1093.5 215.527C1092.73 215.08 1092.25 214.256 1092.25 213.363V168.262C1092.25 167.369 1092.73 166.545 1093.5 166.098L1132.53 143.551C1133.3 143.103 1134.26 143.103 1135.03 143.551Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1215.34 143.118C1216.27 142.581 1217.41 142.581 1218.34 143.118L1257.36 165.665C1258.3 166.201 1258.87 167.191 1258.87 168.262V213.363C1258.87 214.434 1258.3 215.424 1257.36 215.96L1218.34 238.507C1217.41 239.044 1216.27 239.044 1215.34 238.507L1176.31 215.96C1175.38 215.424 1174.81 214.434 1174.81 213.363V168.262C1174.81 167.191 1175.38 166.201 1176.31 165.665L1215.34 143.118Z" fill="#0F172A"/>
<path d="M1218.09 143.551L1257.11 166.098C1257.88 166.545 1258.37 167.369 1258.37 168.262V213.363C1258.37 214.256 1257.88 215.08 1257.11 215.527L1218.09 238.074C1217.32 238.522 1216.36 238.522 1215.59 238.074L1176.56 215.527C1175.79 215.08 1175.31 214.256 1175.31 213.363V168.262C1175.31 167.369 1175.79 166.545 1176.56 166.098L1215.59 143.551C1216.36 143.103 1217.32 143.103 1218.09 143.551Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1298.39 143.118C1299.32 142.581 1300.46 142.581 1301.39 143.118L1340.42 165.665C1341.35 166.201 1341.92 167.191 1341.92 168.262V213.363C1341.92 214.434 1341.35 215.424 1340.42 215.96L1301.39 238.507C1300.46 239.044 1299.32 239.044 1298.39 238.507L1259.37 215.96C1258.44 215.424 1257.86 214.434 1257.86 213.363V168.262C1257.86 167.191 1258.44 166.201 1259.37 165.665L1298.39 143.118Z" fill="#0F172A"/>
<path d="M1301.14 143.551L1340.17 166.098C1340.94 166.545 1341.42 167.369 1341.42 168.262V213.363C1341.42 214.256 1340.94 215.08 1340.17 215.527L1301.14 238.074C1300.37 238.522 1299.41 238.522 1298.64 238.074L1259.62 215.527C1258.85 215.08 1258.37 214.256 1258.37 213.363V168.262C1258.37 167.369 1258.85 166.545 1259.62 166.098L1298.64 143.551C1299.41 143.103 1300.37 143.103 1301.14 143.551Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1381.45 143.118C1382.38 142.581 1383.52 142.581 1384.45 143.118L1423.48 165.665C1424.41 166.201 1424.98 167.191 1424.98 168.262V213.363C1424.98 214.434 1424.41 215.424 1423.48 215.96L1384.45 238.507C1383.52 239.044 1382.38 239.044 1381.45 238.507L1342.42 215.96C1341.49 215.424 1340.92 214.434 1340.92 213.363V168.262C1340.92 167.191 1341.49 166.201 1342.42 165.665L1381.45 143.118Z" fill="#0F172A"/>
<path d="M1384.2 143.551L1423.23 166.098C1424 166.545 1424.48 167.369 1424.48 168.262V213.363C1424.48 214.256 1424 215.08 1423.23 215.527L1384.2 238.074C1383.43 238.522 1382.47 238.522 1381.7 238.074L1342.67 215.527C1341.9 215.08 1341.42 214.256 1341.42 213.363V168.262C1341.42 167.369 1341.9 166.545 1342.67 166.098L1381.7 143.551C1382.47 143.103 1383.43 143.103 1384.2 143.551Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1464.51 143.118C1465.44 142.581 1466.58 142.581 1467.51 143.118L1506.54 165.665C1507.47 166.201 1508.04 167.191 1508.04 168.262V213.363C1508.04 214.434 1507.47 215.424 1506.54 215.96L1467.51 238.507C1466.58 239.044 1465.44 239.044 1464.51 238.507L1425.48 215.96C1424.55 215.424 1423.98 214.434 1423.98 213.363V168.262C1423.98 167.191 1424.55 166.201 1425.48 165.665L1464.51 143.118Z" fill="#0F172A"/>
<path d="M1467.26 143.551L1506.29 166.098C1507.06 166.545 1507.54 167.369 1507.54 168.262V213.363C1507.54 214.256 1507.06 215.08 1506.29 215.527L1467.26 238.074C1466.49 238.522 1465.53 238.522 1464.76 238.074L1425.73 215.527C1424.96 215.08 1424.48 214.256 1424.48 213.363V168.262C1424.48 167.369 1424.96 166.545 1425.73 166.098L1464.76 143.551C1465.53 143.103 1466.49 143.103 1467.26 143.551Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M11.0057 214.243C11.9354 213.706 13.0812 213.706 14.0108 214.243L53.0376 236.79C53.9657 237.326 54.5372 238.316 54.5372 239.387V284.488C54.5372 285.559 53.9657 286.549 53.0376 287.085L14.0108 309.632C13.0812 310.169 11.9354 310.169 11.0057 309.632L-28.0211 287.085C-28.9491 286.549 -29.5206 285.559 -29.5206 284.488V239.387C-29.5206 238.316 -28.9491 237.326 -28.0211 236.79L11.0057 214.243Z" fill="#0F172A"/>
<path d="M13.7604 214.676L52.7873 237.223C53.5605 237.67 54.0368 238.494 54.0368 239.387V284.488C54.0368 285.381 53.5605 286.205 52.7873 286.652L13.7604 309.199C12.9857 309.647 12.0308 309.647 11.2561 309.199L-27.7707 286.652C-28.5441 286.205 -29.0203 285.381 -29.0203 284.488V239.387C-29.0203 238.494 -28.5441 237.67 -27.7707 237.223L11.2561 214.676C12.0308 214.228 12.9857 214.228 13.7604 214.676Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M94.0628 214.243C94.9925 213.706 96.1383 213.706 97.0679 214.243L136.095 236.79C137.023 237.326 137.594 238.316 137.594 239.387V284.488C137.594 285.559 137.023 286.549 136.095 287.085L97.0679 309.632C96.1383 310.169 94.9925 310.169 94.0628 309.632L55.036 287.085C54.108 286.549 53.5365 285.559 53.5365 284.488V239.387C53.5365 238.316 54.108 237.326 55.036 236.79L94.0628 214.243Z" fill="#0F172A"/>
<path d="M96.8175 214.676L135.844 237.223C136.618 237.67 137.094 238.494 137.094 239.387V284.488C137.094 285.381 136.618 286.205 135.844 286.652L96.8175 309.199C96.0428 309.647 95.0879 309.647 94.3132 309.199L55.2864 286.652C54.513 286.205 54.0368 285.381 54.0368 284.488V239.387C54.0368 238.494 54.513 237.67 55.2864 237.223L94.3132 214.676C95.0879 214.228 96.0428 214.228 96.8175 214.676Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M177.119 214.243C178.049 213.706 179.196 213.706 180.126 214.243L219.152 236.79C220.08 237.326 220.651 238.316 220.651 239.387V284.488C220.651 285.559 220.08 286.549 219.152 287.085L180.126 309.632C179.196 310.169 178.049 310.169 177.119 309.632L138.093 287.085C137.165 286.549 136.594 285.559 136.594 284.488V239.387C136.594 238.316 137.165 237.326 138.093 236.79L177.119 214.243Z" fill="#0F172A"/>
<path d="M179.874 214.676L218.901 237.223C219.675 237.67 220.151 238.494 220.151 239.387V284.488C220.151 285.381 219.675 286.205 218.901 286.652L179.874 309.199C179.1 309.647 178.145 309.647 177.371 309.199L138.344 286.652C137.57 286.205 137.094 285.381 137.094 284.488V239.387C137.094 238.494 137.57 237.67 138.344 237.223L177.371 214.676C178.145 214.228 179.1 214.228 179.874 214.676Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M260.177 214.243C261.106 213.706 262.253 213.706 263.183 214.243L302.21 236.79C303.137 237.326 303.709 238.316 303.709 239.387V284.488C303.709 285.559 303.137 286.549 302.21 287.085L263.183 309.632C262.253 310.169 261.106 310.169 260.177 309.632L221.15 287.085C220.222 286.549 219.651 285.559 219.651 284.488V239.387C219.651 238.316 220.222 237.326 221.15 236.79L260.177 214.243Z" fill="#0F172A"/>
<path d="M262.931 214.676L301.958 237.223C302.732 237.67 303.208 238.494 303.208 239.387V284.488C303.208 285.381 302.732 286.205 301.958 286.652L262.931 309.199C262.157 309.647 261.202 309.647 260.428 309.199L221.401 286.652C220.627 286.205 220.151 285.381 220.151 284.488V239.387C220.151 238.494 220.627 237.67 221.401 237.223L260.428 214.676C261.202 214.228 262.157 214.228 262.931 214.676Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M343.234 214.243C344.163 213.706 345.31 213.706 346.24 214.243L385.267 236.79C386.194 237.326 386.766 238.316 386.766 239.387V284.488C386.766 285.559 386.194 286.549 385.267 287.085L346.24 309.632C345.31 310.169 344.163 310.169 343.234 309.632L304.207 287.085C303.279 286.549 302.708 285.559 302.708 284.488V239.387C302.708 238.316 303.279 237.326 304.207 236.79L343.234 214.243Z" fill="#0F172A"/>
<path d="M345.989 214.676L385.015 237.223C385.789 237.67 386.265 238.494 386.265 239.387V284.488C386.265 285.381 385.789 286.205 385.015 286.652L345.989 309.199C345.214 309.647 344.259 309.647 343.485 309.199L304.458 286.652C303.685 286.205 303.208 285.381 303.208 284.488V239.387C303.208 238.494 303.685 237.67 304.458 237.223L343.485 214.676C344.259 214.228 345.214 214.228 345.989 214.676Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M426.291 214.243C427.22 213.706 428.367 213.706 429.297 214.243L468.324 236.79C469.251 237.326 469.823 238.316 469.823 239.387V284.488C469.823 285.559 469.251 286.549 468.324 287.085L429.297 309.632C428.367 310.169 427.22 310.169 426.291 309.632L387.264 287.085C386.336 286.549 385.765 285.559 385.765 284.488V239.387C385.765 238.316 386.336 237.326 387.264 236.79L426.291 214.243Z" fill="#0F172A"/>
<path d="M429.046 214.676L468.073 237.223C468.846 237.67 469.322 238.494 469.322 239.387V284.488C469.322 285.381 468.846 286.205 468.073 286.652L429.046 309.199C428.271 309.647 427.317 309.647 426.542 309.199L387.515 286.652C386.742 286.205 386.265 285.381 386.265 284.488V239.387C386.265 238.494 386.742 237.67 387.515 237.223L426.542 214.676C427.317 214.228 428.271 214.228 429.046 214.676Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M509.348 214.243C510.278 213.706 511.424 213.706 512.354 214.243L551.381 236.79C552.309 237.326 552.88 238.316 552.88 239.387V284.488C552.88 285.559 552.309 286.549 551.381 287.085L512.354 309.632C511.424 310.169 510.278 310.169 509.348 309.632L470.321 287.085C469.393 286.549 468.822 285.559 468.822 284.488V239.387C468.822 238.316 469.393 237.326 470.321 236.79L509.348 214.243Z" fill="#0F172A"/>
<path d="M512.103 214.676L551.13 237.223C551.903 237.67 552.38 238.494 552.38 239.387V284.488C552.38 285.381 551.903 286.205 551.13 286.652L512.103 309.199C511.328 309.647 510.374 309.647 509.599 309.199L470.572 286.652C469.799 286.205 469.322 285.381 469.322 284.488V239.387C469.322 238.494 469.799 237.67 470.572 237.223L509.599 214.676C510.374 214.228 511.328 214.228 512.103 214.676Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M592.405 214.243C593.335 213.706 594.482 213.706 595.411 214.243L634.438 236.79C635.366 237.326 635.937 238.316 635.937 239.387V284.488C635.937 285.559 635.366 286.549 634.438 287.085L595.411 309.632C594.482 310.169 593.335 310.169 592.405 309.632L553.378 287.085C552.451 286.549 551.879 285.559 551.879 284.488V239.387C551.879 238.316 552.451 237.326 553.378 236.79L592.405 214.243Z" fill="#0F172A"/>
<path d="M595.16 214.676L634.187 237.223C634.96 237.67 635.437 238.494 635.437 239.387V284.488C635.437 285.381 634.96 286.205 634.187 286.652L595.16 309.199C594.385 309.647 593.431 309.647 592.656 309.199L553.629 286.652C552.856 286.205 552.38 285.381 552.38 284.488V239.387C552.38 238.494 552.856 237.67 553.629 237.223L592.656 214.676C593.431 214.228 594.385 214.228 595.16 214.676Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M675.462 214.243C676.392 213.706 677.539 213.706 678.468 214.243L717.495 236.79C718.423 237.326 718.994 238.316 718.994 239.387V284.488C718.994 285.559 718.423 286.549 717.495 287.085L678.468 309.632C677.539 310.169 676.392 310.169 675.462 309.632L636.435 287.085C635.508 286.549 634.936 285.559 634.936 284.488V239.387C634.936 238.316 635.508 237.326 636.435 236.79L675.462 214.243Z" fill="#0F172A"/>
<path d="M678.217 214.676L717.244 237.223C718.017 237.67 718.494 238.494 718.494 239.387V284.488C718.494 285.381 718.017 286.205 717.244 286.652L678.217 309.199C677.443 309.647 676.488 309.647 675.713 309.199L636.687 286.652C635.913 286.205 635.437 285.381 635.437 284.488V239.387C635.437 238.494 635.913 237.67 636.687 237.223L675.713 214.676C676.488 214.228 677.443 214.228 678.217 214.676Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M758.519 214.243C759.449 213.706 760.596 213.706 761.525 214.243L800.552 236.79C801.48 237.326 802.051 238.316 802.051 239.387V284.488C802.051 285.559 801.48 286.549 800.552 287.085L761.525 309.632C760.596 310.169 759.449 310.169 758.519 309.632L719.492 287.085C718.565 286.549 717.993 285.559 717.993 284.488V239.387C717.993 238.316 718.565 237.326 719.492 236.79L758.519 214.243Z" fill="#0F172A"/>
<path d="M761.274 214.676L800.301 237.223C801.075 237.67 801.551 238.494 801.551 239.387V284.488C801.551 285.381 801.075 286.205 800.301 286.652L761.274 309.199C760.5 309.647 759.545 309.647 758.771 309.199L719.744 286.652C718.97 286.205 718.494 285.381 718.494 284.488V239.387C718.494 238.494 718.97 237.67 719.744 237.223L758.771 214.676C759.545 214.228 760.5 214.228 761.274 214.676Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M841.576 214.243C842.506 213.706 843.653 213.706 844.583 214.243L883.609 236.79C884.537 237.326 885.108 238.316 885.108 239.387V284.488C885.108 285.559 884.537 286.549 883.609 287.085L844.583 309.632C843.653 310.169 842.506 310.169 841.576 309.632L802.55 287.085C801.622 286.549 801.051 285.559 801.051 284.488V239.387C801.051 238.316 801.622 237.326 802.55 236.79L841.576 214.243Z" fill="#0F172A"/>
<path d="M844.331 214.676L883.358 237.223C884.132 237.67 884.608 238.494 884.608 239.387V284.488C884.608 285.381 884.132 286.205 883.358 286.652L844.331 309.199C843.557 309.647 842.602 309.647 841.828 309.199L802.801 286.652C802.027 286.205 801.551 285.381 801.551 284.488V239.387C801.551 238.494 802.027 237.67 802.801 237.223L841.828 214.676C842.602 214.228 843.557 214.228 844.331 214.676Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M924.634 214.243C925.563 213.706 926.71 213.706 927.64 214.243L966.666 236.79C967.594 237.326 968.165 238.316 968.165 239.387V284.488C968.165 285.559 967.594 286.549 966.666 287.085L927.64 309.632C926.71 310.169 925.563 310.169 924.634 309.632L885.607 287.085C884.679 286.549 884.108 285.559 884.108 284.488V239.387C884.108 238.316 884.679 237.326 885.607 236.79L924.634 214.243Z" fill="#0F172A"/>
<path d="M927.388 214.676L966.415 237.223C967.189 237.67 967.665 238.494 967.665 239.387V284.488C967.665 285.381 967.189 286.205 966.415 286.652L927.388 309.199C926.614 309.647 925.659 309.647 924.885 309.199L885.858 286.652C885.084 286.205 884.608 285.381 884.608 284.488V239.387C884.608 238.494 885.084 237.67 885.858 237.223L924.885 214.676C925.659 214.228 926.614 214.228 927.388 214.676Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1007.69 214.243C1008.62 213.706 1009.76 213.706 1010.69 214.243L1049.72 236.79C1050.65 237.326 1051.22 238.316 1051.22 239.387V284.488C1051.22 285.559 1050.65 286.549 1049.72 287.085L1010.69 309.632C1009.76 310.169 1008.62 310.169 1007.69 309.632L968.664 287.085C967.736 286.549 967.165 285.559 967.165 284.488V239.387C967.165 238.316 967.736 237.326 968.664 236.79L1007.69 214.243Z" fill="#0F172A"/>
<path d="M1010.44 214.676L1049.47 237.223C1050.24 237.67 1050.72 238.494 1050.72 239.387V284.488C1050.72 285.381 1050.24 286.205 1049.47 286.652L1010.44 309.199C1009.67 309.647 1008.71 309.647 1007.94 309.199L968.915 286.652C968.142 286.205 967.665 285.381 967.665 284.488V239.387C967.665 238.494 968.142 237.67 968.915 237.223L1007.94 214.676C1008.71 214.228 1009.67 214.228 1010.44 214.676Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1090.75 214.243C1091.68 213.706 1092.82 213.706 1093.75 214.243L1132.78 236.79C1133.71 237.326 1134.28 238.316 1134.28 239.387V284.488C1134.28 285.559 1133.71 286.549 1132.78 287.085L1093.75 309.632C1092.82 310.169 1091.68 310.169 1090.75 309.632L1051.72 287.085C1050.79 286.549 1050.22 285.559 1050.22 284.488V239.387C1050.22 238.316 1050.79 237.326 1051.72 236.79L1090.75 214.243Z" fill="#0F172A"/>
<path d="M1093.5 214.676L1132.53 237.223C1133.3 237.67 1133.78 238.494 1133.78 239.387V284.488C1133.78 285.381 1133.3 286.205 1132.53 286.652L1093.5 309.199C1092.73 309.647 1091.77 309.647 1091 309.199L1051.97 286.652C1051.2 286.205 1050.72 285.381 1050.72 284.488V239.387C1050.72 238.494 1051.2 237.67 1051.97 237.223L1091 214.676C1091.77 214.228 1092.73 214.228 1093.5 214.676Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1173.81 214.243C1174.74 213.706 1175.88 213.706 1176.81 214.243L1215.84 236.79C1216.77 237.326 1217.34 238.316 1217.34 239.387V284.488C1217.34 285.559 1216.77 286.549 1215.84 287.085L1176.81 309.632C1175.88 310.169 1174.74 310.169 1173.81 309.632L1134.78 287.085C1133.85 286.549 1133.28 285.559 1133.28 284.488V239.387C1133.28 238.316 1133.85 237.326 1134.78 236.79L1173.81 214.243Z" fill="#0F172A"/>
<path d="M1176.56 214.676L1215.59 237.223C1216.36 237.67 1216.84 238.494 1216.84 239.387V284.488C1216.84 285.381 1216.36 286.205 1215.59 286.652L1176.56 309.199C1175.79 309.647 1174.83 309.647 1174.06 309.199L1135.03 286.652C1134.26 286.205 1133.78 285.381 1133.78 284.488V239.387C1133.78 238.494 1134.26 237.67 1135.03 237.223L1174.06 214.676C1174.83 214.228 1175.79 214.228 1176.56 214.676Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1256.86 214.243C1257.79 213.706 1258.94 213.706 1259.87 214.243L1298.89 236.79C1299.82 237.326 1300.39 238.316 1300.39 239.387V284.488C1300.39 285.559 1299.82 286.549 1298.89 287.085L1259.87 309.632C1258.94 310.169 1257.79 310.169 1256.86 309.632L1217.84 287.085C1216.91 286.549 1216.34 285.559 1216.34 284.488V239.387C1216.34 238.316 1216.91 237.326 1217.84 236.79L1256.86 214.243Z" fill="#0F172A"/>
<path d="M1259.62 214.676L1298.64 237.223C1299.41 237.67 1299.89 238.494 1299.89 239.387V284.488C1299.89 285.381 1299.41 286.205 1298.64 286.652L1259.62 309.199C1258.85 309.647 1257.88 309.647 1257.11 309.199L1218.09 286.652C1217.32 286.205 1216.84 285.381 1216.84 284.488V239.387C1216.84 238.494 1217.32 237.67 1218.09 237.223L1257.11 214.676C1257.88 214.228 1258.85 214.228 1259.62 214.676Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1339.92 214.243C1340.85 213.706 1341.99 213.706 1342.92 214.243L1381.95 236.79C1382.88 237.326 1383.45 238.316 1383.45 239.387V284.488C1383.45 285.559 1382.88 286.549 1381.95 287.085L1342.92 309.632C1341.99 310.169 1340.85 310.169 1339.92 309.632L1300.89 287.085C1299.96 286.549 1299.39 285.559 1299.39 284.488V239.387C1299.39 238.316 1299.96 237.326 1300.89 236.79L1339.92 214.243Z" fill="#0F172A"/>
<path d="M1342.67 214.676L1381.7 237.223C1382.47 237.67 1382.95 238.494 1382.95 239.387V284.488C1382.95 285.381 1382.47 286.205 1381.7 286.652L1342.67 309.199C1341.9 309.647 1340.94 309.647 1340.17 309.199L1301.14 286.652C1300.37 286.205 1299.89 285.381 1299.89 284.488V239.387C1299.89 238.494 1300.37 237.67 1301.14 237.223L1340.17 214.676C1340.94 214.228 1341.9 214.228 1342.67 214.676Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1422.98 214.243C1423.91 213.706 1425.05 213.706 1425.98 214.243L1465.01 236.79C1465.94 237.326 1466.51 238.316 1466.51 239.387V284.488C1466.51 285.559 1465.94 286.549 1465.01 287.085L1425.98 309.632C1425.05 310.169 1423.91 310.169 1422.98 309.632L1383.95 287.085C1383.02 286.549 1382.45 285.559 1382.45 284.488V239.387C1382.45 238.316 1383.02 237.326 1383.95 236.79L1422.98 214.243Z" fill="#0F172A"/>
<path d="M1425.73 214.676L1464.76 237.223C1465.53 237.67 1466.01 238.494 1466.01 239.387V284.488C1466.01 285.381 1465.53 286.205 1464.76 286.652L1425.73 309.199C1424.96 309.647 1424 309.647 1423.23 309.199L1384.2 286.652C1383.43 286.205 1382.95 285.381 1382.95 284.488V239.387C1382.95 238.494 1383.43 237.67 1384.2 237.223L1423.23 214.676C1424 214.228 1424.96 214.228 1425.73 214.676Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M52.5343 285.368C53.4639 284.831 54.6097 284.831 55.5393 285.368L94.5662 307.915C95.4942 308.451 96.0657 309.441 96.0657 310.512V355.613C96.0657 356.684 95.4942 357.674 94.5662 358.21L55.5393 380.757C54.6097 381.294 53.4639 381.294 52.5343 380.757L13.5074 358.21C12.5794 357.674 12.0079 356.684 12.0079 355.613V310.512C12.0079 309.441 12.5794 308.451 13.5074 307.915L52.5343 285.368Z" fill="#0F172A"/>
<path d="M55.289 285.801L94.3158 308.348C95.089 308.795 95.5654 309.619 95.5654 310.512V355.613C95.5654 356.506 95.089 357.33 94.3158 357.777L55.289 380.324C54.5142 380.772 53.5594 380.772 52.7846 380.324L13.7578 357.777C12.9845 357.33 12.5082 356.506 12.5082 355.613V310.512C12.5082 309.619 12.9845 308.795 13.7578 308.348L52.7846 285.801C53.5594 285.353 54.5142 285.353 55.289 285.801Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M135.591 285.368C136.521 284.831 137.667 284.831 138.597 285.368L177.624 307.915C178.551 308.451 179.123 309.441 179.123 310.512V355.613C179.123 356.684 178.551 357.674 177.624 358.21L138.597 380.757C137.667 381.294 136.521 381.294 135.591 380.757L96.5646 358.21C95.6365 357.674 95.065 356.684 95.065 355.613V310.512C95.065 309.441 95.6365 308.451 96.5646 307.915L135.591 285.368Z" fill="#0F172A"/>
<path d="M138.346 285.801L177.373 308.348C178.146 308.795 178.623 309.619 178.623 310.512V355.613C178.623 356.506 178.146 357.33 177.373 357.777L138.346 380.324C137.571 380.772 136.617 380.772 135.842 380.324L96.8149 357.777C96.0416 357.33 95.5654 356.506 95.5654 355.613V310.512C95.5654 309.619 96.0416 308.795 96.8149 308.348L135.842 285.801C136.617 285.353 137.571 285.353 138.346 285.801Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M218.648 285.368C219.578 284.831 220.724 284.831 221.654 285.368L260.681 307.915C261.609 308.451 262.18 309.441 262.18 310.512V355.613C262.18 356.684 261.609 357.674 260.681 358.21L221.654 380.757C220.724 381.294 219.578 381.294 218.648 380.757L179.621 358.21C178.694 357.674 178.122 356.684 178.122 355.613V310.512C178.122 309.441 178.694 308.451 179.621 307.915L218.648 285.368Z" fill="#0F172A"/>
<path d="M221.403 285.801L260.43 308.348C261.203 308.795 261.68 309.619 261.68 310.512V355.613C261.68 356.506 261.203 357.33 260.43 357.777L221.403 380.324C220.628 380.772 219.674 380.772 218.899 380.324L179.872 357.777C179.099 357.33 178.622 356.506 178.622 355.613V310.512C178.622 309.619 179.099 308.795 179.872 308.348L218.899 285.801C219.674 285.353 220.628 285.353 221.403 285.801Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M301.705 285.368C302.635 284.831 303.782 284.831 304.711 285.368L343.738 307.915C344.666 308.451 345.237 309.441 345.237 310.512V355.613C345.237 356.684 344.666 357.674 343.738 358.21L304.711 380.757C303.782 381.294 302.635 381.294 301.705 380.757L262.678 358.21C261.751 357.674 261.179 356.684 261.179 355.613V310.512C261.179 309.441 261.751 308.451 262.678 307.915L301.705 285.368Z" fill="#0F172A"/>
<path d="M304.46 285.801L343.487 308.348C344.26 308.795 344.737 309.619 344.737 310.512V355.613C344.737 356.506 344.26 357.33 343.487 357.777L304.46 380.324C303.686 380.772 302.731 380.772 301.956 380.324L262.929 357.777C262.156 357.33 261.68 356.506 261.68 355.613V310.512C261.68 309.619 262.156 308.795 262.929 308.348L301.956 285.801C302.731 285.353 303.686 285.353 304.46 285.801Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M384.762 285.368C385.692 284.831 386.839 284.831 387.768 285.368L426.795 307.915C427.723 308.451 428.294 309.441 428.294 310.512V355.613C428.294 356.684 427.723 357.674 426.795 358.21L387.768 380.757C386.839 381.294 385.692 381.294 384.762 380.757L345.735 358.21C344.808 357.674 344.236 356.684 344.236 355.613V310.512C344.236 309.441 344.808 308.451 345.735 307.915L384.762 285.368Z" fill="#0F172A"/>
<path d="M387.517 285.801L426.544 308.348C427.318 308.795 427.794 309.619 427.794 310.512V355.613C427.794 356.506 427.318 357.33 426.544 357.777L387.517 380.324C386.743 380.772 385.788 380.772 385.013 380.324L345.987 357.777C345.213 357.33 344.737 356.506 344.737 355.613V310.512C344.737 309.619 345.213 308.795 345.987 308.348L385.013 285.801C385.788 285.353 386.743 285.353 387.517 285.801Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M467.819 285.368C468.749 284.831 469.896 284.831 470.825 285.368L509.852 307.915C510.78 308.451 511.351 309.441 511.351 310.512V355.613C511.351 356.684 510.78 357.674 509.852 358.21L470.825 380.757C469.896 381.294 468.749 381.294 467.819 380.757L428.793 358.21C427.865 357.674 427.294 356.684 427.294 355.613V310.512C427.294 309.441 427.865 308.451 428.793 307.915L467.819 285.368Z" fill="#0F172A"/>
<path d="M470.574 285.801L509.601 308.348C510.375 308.795 510.851 309.619 510.851 310.512V355.613C510.851 356.506 510.375 357.33 509.601 357.777L470.574 380.324C469.8 380.772 468.845 380.772 468.071 380.324L429.044 357.777C428.27 357.33 427.794 356.506 427.794 355.613V310.512C427.794 309.619 428.27 308.795 429.044 308.348L468.071 285.801C468.845 285.353 469.8 285.353 470.574 285.801Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M550.877 285.368C551.806 284.831 552.953 284.831 553.883 285.368L592.909 307.915C593.837 308.451 594.408 309.441 594.408 310.512V355.613C594.408 356.684 593.837 357.674 592.909 358.21L553.883 380.757C552.953 381.294 551.806 381.294 550.877 380.757L511.85 358.21C510.922 357.674 510.351 356.684 510.351 355.613V310.512C510.351 309.441 510.922 308.451 511.85 307.915L550.877 285.368Z" fill="#4C1D95" fill-opacity="0.12"/>
<path d="M553.631 285.801L592.658 308.348C593.432 308.795 593.908 309.619 593.908 310.512V355.613C593.908 356.506 593.432 357.33 592.658 357.777L553.631 380.324C552.857 380.772 551.902 380.772 551.128 380.324L512.101 357.777C511.327 357.33 510.851 356.506 510.851 355.613V310.512C510.851 309.619 511.327 308.795 512.101 308.348L551.128 285.801C551.902 285.353 552.857 285.353 553.631 285.801Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M633.934 285.368C634.863 284.831 636.01 284.831 636.94 285.368L675.967 307.915C676.894 308.451 677.466 309.441 677.466 310.512V355.613C677.466 356.684 676.894 357.674 675.967 358.21L636.94 380.757C636.01 381.294 634.863 381.294 633.934 380.757L594.907 358.21C593.979 357.674 593.408 356.684 593.408 355.613V310.512C593.408 309.441 593.979 308.451 594.907 307.915L633.934 285.368Z" fill="#0F172A"/>
<path d="M636.689 285.801L675.715 308.348C676.489 308.795 676.965 309.619 676.965 310.512V355.613C676.965 356.506 676.489 357.33 675.715 357.777L636.689 380.324C635.914 380.772 634.959 380.772 634.185 380.324L595.158 357.777C594.384 357.33 593.908 356.506 593.908 355.613V310.512C593.908 309.619 594.384 308.795 595.158 308.348L634.185 285.801C634.959 285.353 635.914 285.353 636.689 285.801Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M716.991 285.368C717.92 284.831 719.067 284.831 719.997 285.368L759.024 307.915C759.951 308.451 760.523 309.441 760.523 310.512V355.613C760.523 356.684 759.951 357.674 759.024 358.21L719.997 380.757C719.067 381.294 717.92 381.294 716.991 380.757L677.964 358.21C677.036 357.674 676.465 356.684 676.465 355.613V310.512C676.465 309.441 677.036 308.451 677.964 307.915L716.991 285.368Z" fill="#0F172A"/>
<path d="M719.746 285.801L758.772 308.348C759.546 308.795 760.022 309.619 760.022 310.512V355.613C760.022 356.506 759.546 357.33 758.772 357.777L719.746 380.324C718.971 380.772 718.016 380.772 717.242 380.324L678.215 357.777C677.442 357.33 676.965 356.506 676.965 355.613V310.512C676.965 309.619 677.442 308.795 678.215 308.348L717.242 285.801C718.016 285.353 718.971 285.353 719.746 285.801Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M800.048 285.368C800.977 284.831 802.124 284.831 803.054 285.368L842.081 307.915C843.008 308.451 843.58 309.441 843.58 310.512V355.613C843.58 356.684 843.008 357.674 842.081 358.21L803.054 380.757C802.124 381.294 800.977 381.294 800.048 380.757L761.021 358.21C760.093 357.674 759.522 356.684 759.522 355.613V310.512C759.522 309.441 760.093 308.451 761.021 307.915L800.048 285.368Z" fill="#0F172A"/>
<path d="M802.803 285.801L841.83 308.348C842.603 308.795 843.079 309.619 843.079 310.512V355.613C843.079 356.506 842.603 357.33 841.83 357.777L802.803 380.324C802.028 380.772 801.074 380.772 800.299 380.324L761.272 357.777C760.499 357.33 760.022 356.506 760.022 355.613V310.512C760.022 309.619 760.499 308.795 761.272 308.348L800.299 285.801C801.074 285.353 802.028 285.353 802.803 285.801Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M883.105 285.368C884.035 284.831 885.181 284.831 886.111 285.368L925.138 307.915C926.066 308.451 926.637 309.441 926.637 310.512V355.613C926.637 356.684 926.066 357.674 925.138 358.21L886.111 380.757C885.181 381.294 884.035 381.294 883.105 380.757L844.078 358.21C843.15 357.674 842.579 356.684 842.579 355.613V310.512C842.579 309.441 843.15 308.451 844.078 307.915L883.105 285.368Z" fill="#0F172A"/>
<path d="M885.86 285.801L924.887 308.348C925.66 308.795 926.137 309.619 926.137 310.512V355.613C926.137 356.506 925.66 357.33 924.887 357.777L885.86 380.324C885.085 380.772 884.131 380.772 883.356 380.324L844.329 357.777C843.556 357.33 843.079 356.506 843.079 355.613V310.512C843.079 309.619 843.556 308.795 844.329 308.348L883.356 285.801C884.131 285.353 885.085 285.353 885.86 285.801Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M966.162 285.368C967.092 284.831 968.239 284.831 969.168 285.368L1008.19 307.915C1009.12 308.451 1009.69 309.441 1009.69 310.512V355.613C1009.69 356.684 1009.12 357.674 1008.19 358.21L969.168 380.757C968.239 381.294 967.092 381.294 966.162 380.757L927.135 358.21C926.208 357.674 925.636 356.684 925.636 355.613V310.512C925.636 309.441 926.208 308.451 927.135 307.915L966.162 285.368Z" fill="#0F172A"/>
<path d="M968.917 285.801L1007.94 308.348C1008.71 308.795 1009.19 309.619 1009.19 310.512V355.613C1009.19 356.506 1008.71 357.33 1007.94 357.777L968.917 380.324C968.142 380.772 967.188 380.772 966.413 380.324L927.386 357.777C926.613 357.33 926.137 356.506 926.137 355.613V310.512C926.137 309.619 926.613 308.795 927.386 308.348L966.413 285.801C967.188 285.353 968.142 285.353 968.917 285.801Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1049.22 285.368C1050.15 284.831 1051.29 284.831 1052.22 285.368L1091.25 307.915C1092.18 308.451 1092.75 309.441 1092.75 310.512V355.613C1092.75 356.684 1092.18 357.674 1091.25 358.21L1052.22 380.757C1051.29 381.294 1050.15 381.294 1049.22 380.757L1010.19 358.21C1009.26 357.674 1008.69 356.684 1008.69 355.613V310.512C1008.69 309.441 1009.26 308.451 1010.19 307.915L1049.22 285.368Z" fill="#0F172A"/>
<path d="M1051.97 285.801L1091 308.348C1091.77 308.795 1092.25 309.619 1092.25 310.512V355.613C1092.25 356.506 1091.77 357.33 1091 357.777L1051.97 380.324C1051.2 380.772 1050.24 380.772 1049.47 380.324L1010.44 357.777C1009.67 357.33 1009.19 356.506 1009.19 355.613V310.512C1009.19 309.619 1009.67 308.795 1010.44 308.348L1049.47 285.801C1050.24 285.353 1051.2 285.353 1051.97 285.801Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1132.28 285.368C1133.21 284.831 1134.35 284.831 1135.28 285.368L1174.31 307.915C1175.24 308.451 1175.81 309.441 1175.81 310.512V355.613C1175.81 356.684 1175.24 357.674 1174.31 358.21L1135.28 380.757C1134.35 381.294 1133.21 381.294 1132.28 380.757L1093.25 358.21C1092.32 357.674 1091.75 356.684 1091.75 355.613V310.512C1091.75 309.441 1092.32 308.451 1093.25 307.915L1132.28 285.368Z" fill="#0F172A"/>
<path d="M1135.03 285.801L1174.06 308.348C1174.83 308.795 1175.31 309.619 1175.31 310.512V355.613C1175.31 356.506 1174.83 357.33 1174.06 357.777L1135.03 380.324C1134.26 380.772 1133.3 380.772 1132.53 380.324L1093.5 357.777C1092.73 357.33 1092.25 356.506 1092.25 355.613V310.512C1092.25 309.619 1092.73 308.795 1093.5 308.348L1132.53 285.801C1133.3 285.353 1134.26 285.353 1135.03 285.801Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1215.34 285.368C1216.27 284.831 1217.41 284.831 1218.34 285.368L1257.36 307.915C1258.3 308.451 1258.87 309.441 1258.87 310.512V355.613C1258.87 356.684 1258.3 357.674 1257.36 358.21L1218.34 380.757C1217.41 381.294 1216.27 381.294 1215.34 380.757L1176.31 358.21C1175.38 357.674 1174.81 356.684 1174.81 355.613V310.512C1174.81 309.441 1175.38 308.451 1176.31 307.915L1215.34 285.368Z" fill="#0F172A"/>
<path d="M1218.09 285.801L1257.11 308.348C1257.88 308.795 1258.37 309.619 1258.37 310.512V355.613C1258.37 356.506 1257.88 357.33 1257.11 357.777L1218.09 380.324C1217.32 380.772 1216.36 380.772 1215.59 380.324L1176.56 357.777C1175.79 357.33 1175.31 356.506 1175.31 355.613V310.512C1175.31 309.619 1175.79 308.795 1176.56 308.348L1215.59 285.801C1216.36 285.353 1217.32 285.353 1218.09 285.801Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1298.39 285.368C1299.32 284.831 1300.46 284.831 1301.39 285.368L1340.42 307.915C1341.35 308.451 1341.92 309.441 1341.92 310.512V355.613C1341.92 356.684 1341.35 357.674 1340.42 358.21L1301.39 380.757C1300.46 381.294 1299.32 381.294 1298.39 380.757L1259.37 358.21C1258.44 357.674 1257.86 356.684 1257.86 355.613V310.512C1257.86 309.441 1258.44 308.451 1259.37 307.915L1298.39 285.368Z" fill="#0F172A"/>
<path d="M1301.14 285.801L1340.17 308.348C1340.94 308.795 1341.42 309.619 1341.42 310.512V355.613C1341.42 356.506 1340.94 357.33 1340.17 357.777L1301.14 380.324C1300.37 380.772 1299.41 380.772 1298.64 380.324L1259.62 357.777C1258.85 357.33 1258.37 356.506 1258.37 355.613V310.512C1258.37 309.619 1258.85 308.795 1259.62 308.348L1298.64 285.801C1299.41 285.353 1300.37 285.353 1301.14 285.801Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1381.45 285.368C1382.38 284.831 1383.52 284.831 1384.45 285.368L1423.48 307.915C1424.41 308.451 1424.98 309.441 1424.98 310.512V355.613C1424.98 356.684 1424.41 357.674 1423.48 358.21L1384.45 380.757C1383.52 381.294 1382.38 381.294 1381.45 380.757L1342.42 358.21C1341.49 357.674 1340.92 356.684 1340.92 355.613V310.512C1340.92 309.441 1341.49 308.451 1342.42 307.915L1381.45 285.368Z" fill="#0F172A"/>
<path d="M1384.2 285.801L1423.23 308.348C1424 308.795 1424.48 309.619 1424.48 310.512V355.613C1424.48 356.506 1424 357.33 1423.23 357.777L1384.2 380.324C1383.43 380.772 1382.47 380.772 1381.7 380.324L1342.67 357.777C1341.9 357.33 1341.42 356.506 1341.42 355.613V310.512C1341.42 309.619 1341.9 308.795 1342.67 308.348L1381.7 285.801C1382.47 285.353 1383.43 285.353 1384.2 285.801Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1464.51 285.368C1465.44 284.831 1466.58 284.831 1467.51 285.368L1506.54 307.915C1507.47 308.451 1508.04 309.441 1508.04 310.512V355.613C1508.04 356.684 1507.47 357.674 1506.54 358.21L1467.51 380.757C1466.58 381.294 1465.44 381.294 1464.51 380.757L1425.48 358.21C1424.55 357.674 1423.98 356.684 1423.98 355.613V310.512C1423.98 309.441 1424.55 308.451 1425.48 307.915L1464.51 285.368Z" fill="#0F172A"/>
<path d="M1467.26 285.801L1506.29 308.348C1507.06 308.795 1507.54 309.619 1507.54 310.512V355.613C1507.54 356.506 1507.06 357.33 1506.29 357.777L1467.26 380.324C1466.49 380.772 1465.53 380.772 1464.76 380.324L1425.73 357.777C1424.96 357.33 1424.48 356.506 1424.48 355.613V310.512C1424.48 309.619 1424.96 308.795 1425.73 308.348L1464.76 285.801C1465.53 285.353 1466.49 285.353 1467.26 285.801Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M11.0057 356.493C11.9354 355.956 13.0812 355.956 14.0108 356.493L53.0376 379.04C53.9657 379.576 54.5372 380.566 54.5372 381.637V426.738C54.5372 427.809 53.9657 428.799 53.0376 429.335L14.0108 451.882C13.0812 452.419 11.9354 452.419 11.0057 451.882L-28.0211 429.335C-28.9491 428.799 -29.5206 427.809 -29.5206 426.738V381.637C-29.5206 380.566 -28.9491 379.576 -28.0211 379.04L11.0057 356.493Z" fill="#0F172A"/>
<path d="M13.7604 356.926L52.7873 379.473C53.5605 379.92 54.0368 380.744 54.0368 381.637V426.738C54.0368 427.631 53.5605 428.455 52.7873 428.902L13.7604 451.449C12.9857 451.897 12.0308 451.897 11.2561 451.449L-27.7707 428.902C-28.5441 428.455 -29.0203 427.631 -29.0203 426.738V381.637C-29.0203 380.744 -28.5441 379.92 -27.7707 379.473L11.2561 356.926C12.0308 356.478 12.9857 356.478 13.7604 356.926Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M94.0628 356.493C94.9925 355.956 96.1383 355.956 97.0679 356.493L136.095 379.04C137.023 379.576 137.594 380.566 137.594 381.637V426.738C137.594 427.809 137.023 428.799 136.095 429.335L97.0679 451.882C96.1383 452.419 94.9925 452.419 94.0628 451.882L55.036 429.335C54.108 428.799 53.5365 427.809 53.5365 426.738V381.637C53.5365 380.566 54.108 379.576 55.036 379.04L94.0628 356.493Z" fill="#0F172A"/>
<path d="M96.8175 356.926L135.844 379.473C136.618 379.92 137.094 380.744 137.094 381.637V426.738C137.094 427.631 136.618 428.455 135.844 428.902L96.8175 451.449C96.0428 451.897 95.0879 451.897 94.3132 451.449L55.2864 428.902C54.513 428.455 54.0368 427.631 54.0368 426.738V381.637C54.0368 380.744 54.513 379.92 55.2864 379.473L94.3132 356.926C95.0879 356.478 96.0428 356.478 96.8175 356.926Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M177.119 356.493C178.049 355.956 179.196 355.956 180.126 356.493L219.152 379.04C220.08 379.576 220.651 380.566 220.651 381.637V426.738C220.651 427.809 220.08 428.799 219.152 429.335L180.126 451.882C179.196 452.419 178.049 452.419 177.119 451.882L138.093 429.335C137.165 428.799 136.594 427.809 136.594 426.738V381.637C136.594 380.566 137.165 379.576 138.093 379.04L177.119 356.493Z" fill="#0F172A"/>
<path d="M179.874 356.926L218.901 379.473C219.675 379.92 220.151 380.744 220.151 381.637V426.738C220.151 427.631 219.675 428.455 218.901 428.902L179.874 451.449C179.1 451.897 178.145 451.897 177.371 451.449L138.344 428.902C137.57 428.455 137.094 427.631 137.094 426.738V381.637C137.094 380.744 137.57 379.92 138.344 379.473L177.371 356.926C178.145 356.478 179.1 356.478 179.874 356.926Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M260.177 356.493C261.106 355.956 262.253 355.956 263.183 356.493L302.21 379.04C303.137 379.576 303.709 380.566 303.709 381.637V426.738C303.709 427.809 303.137 428.799 302.21 429.335L263.183 451.882C262.253 452.419 261.106 452.419 260.177 451.882L221.15 429.335C220.222 428.799 219.651 427.809 219.651 426.738V381.637C219.651 380.566 220.222 379.576 221.15 379.04L260.177 356.493Z" fill="#0F172A"/>
<path d="M262.931 356.926L301.958 379.473C302.732 379.92 303.208 380.744 303.208 381.637V426.738C303.208 427.631 302.732 428.455 301.958 428.902L262.931 451.449C262.157 451.897 261.202 451.897 260.428 451.449L221.401 428.902C220.627 428.455 220.151 427.631 220.151 426.738V381.637C220.151 380.744 220.627 379.92 221.401 379.473L260.428 356.926C261.202 356.478 262.157 356.478 262.931 356.926Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M343.234 356.493C344.163 355.956 345.31 355.956 346.24 356.493L385.267 379.04C386.194 379.576 386.766 380.566 386.766 381.637V426.738C386.766 427.809 386.194 428.799 385.267 429.335L346.24 451.882C345.31 452.419 344.163 452.419 343.234 451.882L304.207 429.335C303.279 428.799 302.708 427.809 302.708 426.738V381.637C302.708 380.566 303.279 379.576 304.207 379.04L343.234 356.493Z" fill="#0F172A"/>
<path d="M345.989 356.926L385.015 379.473C385.789 379.92 386.265 380.744 386.265 381.637V426.738C386.265 427.631 385.789 428.455 385.015 428.902L345.989 451.449C345.214 451.897 344.259 451.897 343.485 451.449L304.458 428.902C303.685 428.455 303.208 427.631 303.208 426.738V381.637C303.208 380.744 303.685 379.92 304.458 379.473L343.485 356.926C344.259 356.478 345.214 356.478 345.989 356.926Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M426.291 356.493C427.22 355.956 428.367 355.956 429.297 356.493L468.324 379.04C469.251 379.576 469.823 380.566 469.823 381.637V426.738C469.823 427.809 469.251 428.799 468.324 429.335L429.297 451.882C428.367 452.419 427.22 452.419 426.291 451.882L387.264 429.335C386.336 428.799 385.765 427.809 385.765 426.738V381.637C385.765 380.566 386.336 379.576 387.264 379.04L426.291 356.493Z" fill="#0F172A"/>
<path d="M429.046 356.926L468.073 379.473C468.846 379.92 469.322 380.744 469.322 381.637V426.738C469.322 427.631 468.846 428.455 468.073 428.902L429.046 451.449C428.271 451.897 427.317 451.897 426.542 451.449L387.515 428.902C386.742 428.455 386.265 427.631 386.265 426.738V381.637C386.265 380.744 386.742 379.92 387.515 379.473L426.542 356.926C427.317 356.478 428.271 356.478 429.046 356.926Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M509.348 356.493C510.278 355.956 511.424 355.956 512.354 356.493L551.381 379.04C552.309 379.576 552.88 380.566 552.88 381.637V426.738C552.88 427.809 552.309 428.799 551.381 429.335L512.354 451.882C511.424 452.419 510.278 452.419 509.348 451.882L470.321 429.335C469.393 428.799 468.822 427.809 468.822 426.738V381.637C468.822 380.566 469.393 379.576 470.321 379.04L509.348 356.493Z" fill="#0F172A"/>
<path d="M512.103 356.926L551.13 379.473C551.903 379.92 552.38 380.744 552.38 381.637V426.738C552.38 427.631 551.903 428.455 551.13 428.902L512.103 451.449C511.328 451.897 510.374 451.897 509.599 451.449L470.572 428.902C469.799 428.455 469.322 427.631 469.322 426.738V381.637C469.322 380.744 469.799 379.92 470.572 379.473L509.599 356.926C510.374 356.478 511.328 356.478 512.103 356.926Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M592.405 356.493C593.335 355.956 594.482 355.956 595.411 356.493L634.438 379.04C635.366 379.576 635.937 380.566 635.937 381.637V426.738C635.937 427.809 635.366 428.799 634.438 429.335L595.411 451.882C594.482 452.419 593.335 452.419 592.405 451.882L553.378 429.335C552.451 428.799 551.879 427.809 551.879 426.738V381.637C551.879 380.566 552.451 379.576 553.378 379.04L592.405 356.493Z" fill="#0F172A"/>
<path d="M595.16 356.926L634.187 379.473C634.96 379.92 635.437 380.744 635.437 381.637V426.738C635.437 427.631 634.96 428.455 634.187 428.902L595.16 451.449C594.385 451.897 593.431 451.897 592.656 451.449L553.629 428.902C552.856 428.455 552.38 427.631 552.38 426.738V381.637C552.38 380.744 552.856 379.92 553.629 379.473L592.656 356.926C593.431 356.478 594.385 356.478 595.16 356.926Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M675.462 356.493C676.392 355.956 677.539 355.956 678.468 356.493L717.495 379.04C718.423 379.576 718.994 380.566 718.994 381.637V426.738C718.994 427.809 718.423 428.799 717.495 429.335L678.468 451.882C677.539 452.419 676.392 452.419 675.462 451.882L636.435 429.335C635.508 428.799 634.936 427.809 634.936 426.738V381.637C634.936 380.566 635.508 379.576 636.435 379.04L675.462 356.493Z" fill="#0F172A"/>
<path d="M678.217 356.926L717.244 379.473C718.017 379.92 718.494 380.744 718.494 381.637V426.738C718.494 427.631 718.017 428.455 717.244 428.902L678.217 451.449C677.443 451.897 676.488 451.897 675.713 451.449L636.687 428.902C635.913 428.455 635.437 427.631 635.437 426.738V381.637C635.437 380.744 635.913 379.92 636.687 379.473L675.713 356.926C676.488 356.478 677.443 356.478 678.217 356.926Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M758.519 356.493C759.449 355.956 760.596 355.956 761.525 356.493L800.552 379.04C801.48 379.576 802.051 380.566 802.051 381.637V426.738C802.051 427.809 801.48 428.799 800.552 429.335L761.525 451.882C760.596 452.419 759.449 452.419 758.519 451.882L719.492 429.335C718.565 428.799 717.993 427.809 717.993 426.738V381.637C717.993 380.566 718.565 379.576 719.492 379.04L758.519 356.493Z" fill="#0F172A"/>
<path d="M761.274 356.926L800.301 379.473C801.075 379.92 801.551 380.744 801.551 381.637V426.738C801.551 427.631 801.075 428.455 800.301 428.902L761.274 451.449C760.5 451.897 759.545 451.897 758.771 451.449L719.744 428.902C718.97 428.455 718.494 427.631 718.494 426.738V381.637C718.494 380.744 718.97 379.92 719.744 379.473L758.771 356.926C759.545 356.478 760.5 356.478 761.274 356.926Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M841.576 356.493C842.506 355.956 843.653 355.956 844.583 356.493L883.609 379.04C884.537 379.576 885.108 380.566 885.108 381.637V426.738C885.108 427.809 884.537 428.799 883.609 429.335L844.583 451.882C843.653 452.419 842.506 452.419 841.576 451.882L802.55 429.335C801.622 428.799 801.051 427.809 801.051 426.738V381.637C801.051 380.566 801.622 379.576 802.55 379.04L841.576 356.493Z" fill="#4C1D95" fill-opacity="0.12"/>
<path d="M844.331 356.926L883.358 379.473C884.132 379.92 884.608 380.744 884.608 381.637V426.738C884.608 427.631 884.132 428.455 883.358 428.902L844.331 451.449C843.557 451.897 842.602 451.897 841.828 451.449L802.801 428.902C802.027 428.455 801.551 427.631 801.551 426.738V381.637C801.551 380.744 802.027 379.92 802.801 379.473L841.828 356.926C842.602 356.478 843.557 356.478 844.331 356.926Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M924.634 356.493C925.563 355.956 926.71 355.956 927.64 356.493L966.666 379.04C967.594 379.576 968.165 380.566 968.165 381.637V426.738C968.165 427.809 967.594 428.799 966.666 429.335L927.64 451.882C926.71 452.419 925.563 452.419 924.634 451.882L885.607 429.335C884.679 428.799 884.108 427.809 884.108 426.738V381.637C884.108 380.566 884.679 379.576 885.607 379.04L924.634 356.493Z" fill="#4C1D95" fill-opacity="0.12"/>
<path d="M927.388 356.926L966.415 379.473C967.189 379.92 967.665 380.744 967.665 381.637V426.738C967.665 427.631 967.189 428.455 966.415 428.902L927.388 451.449C926.614 451.897 925.659 451.897 924.885 451.449L885.858 428.902C885.084 428.455 884.608 427.631 884.608 426.738V381.637C884.608 380.744 885.084 379.92 885.858 379.473L924.885 356.926C925.659 356.478 926.614 356.478 927.388 356.926Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1007.69 356.493C1008.62 355.956 1009.76 355.956 1010.69 356.493L1049.72 379.04C1050.65 379.576 1051.22 380.566 1051.22 381.637V426.738C1051.22 427.809 1050.65 428.799 1049.72 429.335L1010.69 451.882C1009.76 452.419 1008.62 452.419 1007.69 451.882L968.664 429.335C967.736 428.799 967.165 427.809 967.165 426.738V381.637C967.165 380.566 967.736 379.576 968.664 379.04L1007.69 356.493Z" fill="#0F172A"/>
<path d="M1010.44 356.926L1049.47 379.473C1050.24 379.92 1050.72 380.744 1050.72 381.637V426.738C1050.72 427.631 1050.24 428.455 1049.47 428.902L1010.44 451.449C1009.67 451.897 1008.71 451.897 1007.94 451.449L968.915 428.902C968.142 428.455 967.665 427.631 967.665 426.738V381.637C967.665 380.744 968.142 379.92 968.915 379.473L1007.94 356.926C1008.71 356.478 1009.67 356.478 1010.44 356.926Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1090.75 356.493C1091.68 355.956 1092.82 355.956 1093.75 356.493L1132.78 379.04C1133.71 379.576 1134.28 380.566 1134.28 381.637V426.738C1134.28 427.809 1133.71 428.799 1132.78 429.335L1093.75 451.882C1092.82 452.419 1091.68 452.419 1090.75 451.882L1051.72 429.335C1050.79 428.799 1050.22 427.809 1050.22 426.738V381.637C1050.22 380.566 1050.79 379.576 1051.72 379.04L1090.75 356.493Z" fill="#0F172A"/>
<path d="M1093.5 356.926L1132.53 379.473C1133.3 379.92 1133.78 380.744 1133.78 381.637V426.738C1133.78 427.631 1133.3 428.455 1132.53 428.902L1093.5 451.449C1092.73 451.897 1091.77 451.897 1091 451.449L1051.97 428.902C1051.2 428.455 1050.72 427.631 1050.72 426.738V381.637C1050.72 380.744 1051.2 379.92 1051.97 379.473L1091 356.926C1091.77 356.478 1092.73 356.478 1093.5 356.926Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1173.81 356.493C1174.74 355.956 1175.88 355.956 1176.81 356.493L1215.84 379.04C1216.77 379.576 1217.34 380.566 1217.34 381.637V426.738C1217.34 427.809 1216.77 428.799 1215.84 429.335L1176.81 451.882C1175.88 452.419 1174.74 452.419 1173.81 451.882L1134.78 429.335C1133.85 428.799 1133.28 427.809 1133.28 426.738V381.637C1133.28 380.566 1133.85 379.576 1134.78 379.04L1173.81 356.493Z" fill="#0F172A"/>
<path d="M1176.56 356.926L1215.59 379.473C1216.36 379.92 1216.84 380.744 1216.84 381.637V426.738C1216.84 427.631 1216.36 428.455 1215.59 428.902L1176.56 451.449C1175.79 451.897 1174.83 451.897 1174.06 451.449L1135.03 428.902C1134.26 428.455 1133.78 427.631 1133.78 426.738V381.637C1133.78 380.744 1134.26 379.92 1135.03 379.473L1174.06 356.926C1174.83 356.478 1175.79 356.478 1176.56 356.926Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1256.86 356.493C1257.79 355.956 1258.94 355.956 1259.87 356.493L1298.89 379.04C1299.82 379.576 1300.39 380.566 1300.39 381.637V426.738C1300.39 427.809 1299.82 428.799 1298.89 429.335L1259.87 451.882C1258.94 452.419 1257.79 452.419 1256.86 451.882L1217.84 429.335C1216.91 428.799 1216.34 427.809 1216.34 426.738V381.637C1216.34 380.566 1216.91 379.576 1217.84 379.04L1256.86 356.493Z" fill="#0F172A"/>
<path d="M1259.62 356.926L1298.64 379.473C1299.41 379.92 1299.89 380.744 1299.89 381.637V426.738C1299.89 427.631 1299.41 428.455 1298.64 428.902L1259.62 451.449C1258.85 451.897 1257.88 451.897 1257.11 451.449L1218.09 428.902C1217.32 428.455 1216.84 427.631 1216.84 426.738V381.637C1216.84 380.744 1217.32 379.92 1218.09 379.473L1257.11 356.926C1257.88 356.478 1258.85 356.478 1259.62 356.926Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1339.92 356.493C1340.85 355.956 1341.99 355.956 1342.92 356.493L1381.95 379.04C1382.88 379.576 1383.45 380.566 1383.45 381.637V426.738C1383.45 427.809 1382.88 428.799 1381.95 429.335L1342.92 451.882C1341.99 452.419 1340.85 452.419 1339.92 451.882L1300.89 429.335C1299.96 428.799 1299.39 427.809 1299.39 426.738V381.637C1299.39 380.566 1299.96 379.576 1300.89 379.04L1339.92 356.493Z" fill="#0F172A"/>
<path d="M1342.67 356.926L1381.7 379.473C1382.47 379.92 1382.95 380.744 1382.95 381.637V426.738C1382.95 427.631 1382.47 428.455 1381.7 428.902L1342.67 451.449C1341.9 451.897 1340.94 451.897 1340.17 451.449L1301.14 428.902C1300.37 428.455 1299.89 427.631 1299.89 426.738V381.637C1299.89 380.744 1300.37 379.92 1301.14 379.473L1340.17 356.926C1340.94 356.478 1341.9 356.478 1342.67 356.926Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1422.98 356.493C1423.91 355.956 1425.05 355.956 1425.98 356.493L1465.01 379.04C1465.94 379.576 1466.51 380.566 1466.51 381.637V426.738C1466.51 427.809 1465.94 428.799 1465.01 429.335L1425.98 451.882C1425.05 452.419 1423.91 452.419 1422.98 451.882L1383.95 429.335C1383.02 428.799 1382.45 427.809 1382.45 426.738V381.637C1382.45 380.566 1383.02 379.576 1383.95 379.04L1422.98 356.493Z" fill="#0F172A"/>
<path d="M1425.73 356.926L1464.76 379.473C1465.53 379.92 1466.01 380.744 1466.01 381.637V426.738C1466.01 427.631 1465.53 428.455 1464.76 428.902L1425.73 451.449C1424.96 451.897 1424 451.897 1423.23 451.449L1384.2 428.902C1383.43 428.455 1382.95 427.631 1382.95 426.738V381.637C1382.95 380.744 1383.43 379.92 1384.2 379.473L1423.23 356.926C1424 356.478 1424.96 356.478 1425.73 356.926Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M52.5343 427.618C53.4639 427.081 54.6097 427.081 55.5393 427.618L94.5662 450.165C95.4942 450.701 96.0657 451.691 96.0657 452.762V497.863C96.0657 498.934 95.4942 499.924 94.5662 500.46L55.5393 523.007C54.6097 523.544 53.4639 523.544 52.5343 523.007L13.5074 500.46C12.5794 499.924 12.0079 498.934 12.0079 497.863V452.762C12.0079 451.691 12.5794 450.701 13.5074 450.165L52.5343 427.618Z" fill="#0F172A"/>
<path d="M55.289 428.051L94.3158 450.598C95.089 451.045 95.5654 451.869 95.5654 452.762V497.863C95.5654 498.756 95.089 499.58 94.3158 500.027L55.289 522.574C54.5142 523.022 53.5594 523.022 52.7846 522.574L13.7578 500.027C12.9845 499.58 12.5082 498.756 12.5082 497.863V452.762C12.5082 451.869 12.9845 451.045 13.7578 450.598L52.7846 428.051C53.5594 427.603 54.5142 427.603 55.289 428.051Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M135.591 427.618C136.521 427.081 137.667 427.081 138.597 427.618L177.624 450.165C178.551 450.701 179.123 451.691 179.123 452.762V497.863C179.123 498.934 178.551 499.924 177.624 500.46L138.597 523.007C137.667 523.544 136.521 523.544 135.591 523.007L96.5646 500.46C95.6365 499.924 95.065 498.934 95.065 497.863V452.762C95.065 451.691 95.6365 450.701 96.5646 450.165L135.591 427.618Z" fill="#0F172A"/>
<path d="M138.346 428.051L177.373 450.598C178.146 451.045 178.623 451.869 178.623 452.762V497.863C178.623 498.756 178.146 499.58 177.373 500.027L138.346 522.574C137.571 523.022 136.617 523.022 135.842 522.574L96.8149 500.027C96.0416 499.58 95.5654 498.756 95.5654 497.863V452.762C95.5654 451.869 96.0416 451.045 96.8149 450.598L135.842 428.051C136.617 427.603 137.571 427.603 138.346 428.051Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M218.648 427.618C219.578 427.081 220.724 427.081 221.654 427.618L260.681 450.165C261.609 450.701 262.18 451.691 262.18 452.762V497.863C262.18 498.934 261.609 499.924 260.681 500.46L221.654 523.007C220.724 523.544 219.578 523.544 218.648 523.007L179.621 500.46C178.694 499.924 178.122 498.934 178.122 497.863V452.762C178.122 451.691 178.694 450.701 179.621 450.165L218.648 427.618Z" fill="#0F172A"/>
<path d="M221.403 428.051L260.43 450.598C261.203 451.045 261.68 451.869 261.68 452.762V497.863C261.68 498.756 261.203 499.58 260.43 500.027L221.403 522.574C220.628 523.022 219.674 523.022 218.899 522.574L179.872 500.027C179.099 499.58 178.622 498.756 178.622 497.863V452.762C178.622 451.869 179.099 451.045 179.872 450.598L218.899 428.051C219.674 427.603 220.628 427.603 221.403 428.051Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M301.705 427.618C302.635 427.081 303.782 427.081 304.711 427.618L343.738 450.165C344.666 450.701 345.237 451.691 345.237 452.762V497.863C345.237 498.934 344.666 499.924 343.738 500.46L304.711 523.007C303.782 523.544 302.635 523.544 301.705 523.007L262.678 500.46C261.751 499.924 261.179 498.934 261.179 497.863V452.762C261.179 451.691 261.751 450.701 262.678 450.165L301.705 427.618Z" fill="#0F172A"/>
<path d="M304.46 428.051L343.487 450.598C344.26 451.045 344.737 451.869 344.737 452.762V497.863C344.737 498.756 344.26 499.58 343.487 500.027L304.46 522.574C303.686 523.022 302.731 523.022 301.956 522.574L262.929 500.027C262.156 499.58 261.68 498.756 261.68 497.863V452.762C261.68 451.869 262.156 451.045 262.929 450.598L301.956 428.051C302.731 427.603 303.686 427.603 304.46 428.051Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M384.762 427.618C385.692 427.081 386.839 427.081 387.768 427.618L426.795 450.165C427.723 450.701 428.294 451.691 428.294 452.762V497.863C428.294 498.934 427.723 499.924 426.795 500.46L387.768 523.007C386.839 523.544 385.692 523.544 384.762 523.007L345.735 500.46C344.808 499.924 344.236 498.934 344.236 497.863V452.762C344.236 451.691 344.808 450.701 345.735 450.165L384.762 427.618Z" fill="#0F172A"/>
<path d="M387.517 428.051L426.544 450.598C427.318 451.045 427.794 451.869 427.794 452.762V497.863C427.794 498.756 427.318 499.58 426.544 500.027L387.517 522.574C386.743 523.022 385.788 523.022 385.013 522.574L345.987 500.027C345.213 499.58 344.737 498.756 344.737 497.863V452.762C344.737 451.869 345.213 451.045 345.987 450.598L385.013 428.051C385.788 427.603 386.743 427.603 387.517 428.051Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M467.819 427.618C468.749 427.081 469.896 427.081 470.825 427.618L509.852 450.165C510.78 450.701 511.351 451.691 511.351 452.762V497.863C511.351 498.934 510.78 499.924 509.852 500.46L470.825 523.007C469.896 523.544 468.749 523.544 467.819 523.007L428.793 500.46C427.865 499.924 427.294 498.934 427.294 497.863V452.762C427.294 451.691 427.865 450.701 428.793 450.165L467.819 427.618Z" fill="#0F172A"/>
<path d="M470.574 428.051L509.601 450.598C510.375 451.045 510.851 451.869 510.851 452.762V497.863C510.851 498.756 510.375 499.58 509.601 500.027L470.574 522.574C469.8 523.022 468.845 523.022 468.071 522.574L429.044 500.027C428.27 499.58 427.794 498.756 427.794 497.863V452.762C427.794 451.869 428.27 451.045 429.044 450.598L468.071 428.051C468.845 427.603 469.8 427.603 470.574 428.051Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M550.877 427.618C551.806 427.081 552.953 427.081 553.883 427.618L592.909 450.165C593.837 450.701 594.408 451.691 594.408 452.762V497.863C594.408 498.934 593.837 499.924 592.909 500.46L553.883 523.007C552.953 523.544 551.806 523.544 550.877 523.007L511.85 500.46C510.922 499.924 510.351 498.934 510.351 497.863V452.762C510.351 451.691 510.922 450.701 511.85 450.165L550.877 427.618Z" fill="#0F172A"/>
<path d="M553.631 428.051L592.658 450.598C593.432 451.045 593.908 451.869 593.908 452.762V497.863C593.908 498.756 593.432 499.58 592.658 500.027L553.631 522.574C552.857 523.022 551.902 523.022 551.128 522.574L512.101 500.027C511.327 499.58 510.851 498.756 510.851 497.863V452.762C510.851 451.869 511.327 451.045 512.101 450.598L551.128 428.051C551.902 427.603 552.857 427.603 553.631 428.051Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M633.934 427.618C634.863 427.081 636.01 427.081 636.94 427.618L675.967 450.165C676.894 450.701 677.466 451.691 677.466 452.762V497.863C677.466 498.934 676.894 499.924 675.967 500.46L636.94 523.007C636.01 523.544 634.863 523.544 633.934 523.007L594.907 500.46C593.979 499.924 593.408 498.934 593.408 497.863V452.762C593.408 451.691 593.979 450.701 594.907 450.165L633.934 427.618Z" fill="#0F172A"/>
<path d="M636.689 428.051L675.715 450.598C676.489 451.045 676.965 451.869 676.965 452.762V497.863C676.965 498.756 676.489 499.58 675.715 500.027L636.689 522.574C635.914 523.022 634.959 523.022 634.185 522.574L595.158 500.027C594.384 499.58 593.908 498.756 593.908 497.863V452.762C593.908 451.869 594.384 451.045 595.158 450.598L634.185 428.051C634.959 427.603 635.914 427.603 636.689 428.051Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M716.991 427.618C717.92 427.081 719.067 427.081 719.997 427.618L759.024 450.165C759.951 450.701 760.523 451.691 760.523 452.762V497.863C760.523 498.934 759.951 499.924 759.024 500.46L719.997 523.007C719.067 523.544 717.92 523.544 716.991 523.007L677.964 500.46C677.036 499.924 676.465 498.934 676.465 497.863V452.762C676.465 451.691 677.036 450.701 677.964 450.165L716.991 427.618Z" fill="#0F172A"/>
<path d="M719.746 428.051L758.772 450.598C759.546 451.045 760.022 451.869 760.022 452.762V497.863C760.022 498.756 759.546 499.58 758.772 500.027L719.746 522.574C718.971 523.022 718.016 523.022 717.242 522.574L678.215 500.027C677.442 499.58 676.965 498.756 676.965 497.863V452.762C676.965 451.869 677.442 451.045 678.215 450.598L717.242 428.051C718.016 427.603 718.971 427.603 719.746 428.051Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M800.048 427.618C800.977 427.081 802.124 427.081 803.054 427.618L842.081 450.165C843.008 450.701 843.58 451.691 843.58 452.762V497.863C843.58 498.934 843.008 499.924 842.081 500.46L803.054 523.007C802.124 523.544 800.977 523.544 800.048 523.007L761.021 500.46C760.093 499.924 759.522 498.934 759.522 497.863V452.762C759.522 451.691 760.093 450.701 761.021 450.165L800.048 427.618Z" fill="#0F172A"/>
<path d="M802.803 428.051L841.83 450.598C842.603 451.045 843.079 451.869 843.079 452.762V497.863C843.079 498.756 842.603 499.58 841.83 500.027L802.803 522.574C802.028 523.022 801.074 523.022 800.299 522.574L761.272 500.027C760.499 499.58 760.022 498.756 760.022 497.863V452.762C760.022 451.869 760.499 451.045 761.272 450.598L800.299 428.051C801.074 427.603 802.028 427.603 802.803 428.051Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M883.105 427.618C884.035 427.081 885.181 427.081 886.111 427.618L925.138 450.165C926.066 450.701 926.637 451.691 926.637 452.762V497.863C926.637 498.934 926.066 499.924 925.138 500.46L886.111 523.007C885.181 523.544 884.035 523.544 883.105 523.007L844.078 500.46C843.15 499.924 842.579 498.934 842.579 497.863V452.762C842.579 451.691 843.15 450.701 844.078 450.165L883.105 427.618Z" fill="#0F172A"/>
<path d="M885.86 428.051L924.887 450.598C925.66 451.045 926.137 451.869 926.137 452.762V497.863C926.137 498.756 925.66 499.58 924.887 500.027L885.86 522.574C885.085 523.022 884.131 523.022 883.356 522.574L844.329 500.027C843.556 499.58 843.079 498.756 843.079 497.863V452.762C843.079 451.869 843.556 451.045 844.329 450.598L883.356 428.051C884.131 427.603 885.085 427.603 885.86 428.051Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M966.162 427.618C967.092 427.081 968.239 427.081 969.168 427.618L1008.19 450.165C1009.12 450.701 1009.69 451.691 1009.69 452.762V497.863C1009.69 498.934 1009.12 499.924 1008.19 500.46L969.168 523.007C968.239 523.544 967.092 523.544 966.162 523.007L927.135 500.46C926.208 499.924 925.636 498.934 925.636 497.863V452.762C925.636 451.691 926.208 450.701 927.135 450.165L966.162 427.618Z" fill="#0F172A"/>
<path d="M968.917 428.051L1007.94 450.598C1008.71 451.045 1009.19 451.869 1009.19 452.762V497.863C1009.19 498.756 1008.71 499.58 1007.94 500.027L968.917 522.574C968.142 523.022 967.188 523.022 966.413 522.574L927.386 500.027C926.613 499.58 926.137 498.756 926.137 497.863V452.762C926.137 451.869 926.613 451.045 927.386 450.598L966.413 428.051C967.188 427.603 968.142 427.603 968.917 428.051Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1049.22 427.618C1050.15 427.081 1051.29 427.081 1052.22 427.618L1091.25 450.165C1092.18 450.701 1092.75 451.691 1092.75 452.762V497.863C1092.75 498.934 1092.18 499.924 1091.25 500.46L1052.22 523.007C1051.29 523.544 1050.15 523.544 1049.22 523.007L1010.19 500.46C1009.26 499.924 1008.69 498.934 1008.69 497.863V452.762C1008.69 451.691 1009.26 450.701 1010.19 450.165L1049.22 427.618Z" fill="#0F172A"/>
<path d="M1051.97 428.051L1091 450.598C1091.77 451.045 1092.25 451.869 1092.25 452.762V497.863C1092.25 498.756 1091.77 499.58 1091 500.027L1051.97 522.574C1051.2 523.022 1050.24 523.022 1049.47 522.574L1010.44 500.027C1009.67 499.58 1009.19 498.756 1009.19 497.863V452.762C1009.19 451.869 1009.67 451.045 1010.44 450.598L1049.47 428.051C1050.24 427.603 1051.2 427.603 1051.97 428.051Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1132.28 427.618C1133.21 427.081 1134.35 427.081 1135.28 427.618L1174.31 450.165C1175.24 450.701 1175.81 451.691 1175.81 452.762V497.863C1175.81 498.934 1175.24 499.924 1174.31 500.46L1135.28 523.007C1134.35 523.544 1133.21 523.544 1132.28 523.007L1093.25 500.46C1092.32 499.924 1091.75 498.934 1091.75 497.863V452.762C1091.75 451.691 1092.32 450.701 1093.25 450.165L1132.28 427.618Z" fill="#0F172A"/>
<path d="M1135.03 428.051L1174.06 450.598C1174.83 451.045 1175.31 451.869 1175.31 452.762V497.863C1175.31 498.756 1174.83 499.58 1174.06 500.027L1135.03 522.574C1134.26 523.022 1133.3 523.022 1132.53 522.574L1093.5 500.027C1092.73 499.58 1092.25 498.756 1092.25 497.863V452.762C1092.25 451.869 1092.73 451.045 1093.5 450.598L1132.53 428.051C1133.3 427.603 1134.26 427.603 1135.03 428.051Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1215.34 427.618C1216.27 427.081 1217.41 427.081 1218.34 427.618L1257.36 450.165C1258.3 450.701 1258.87 451.691 1258.87 452.762V497.863C1258.87 498.934 1258.3 499.924 1257.36 500.46L1218.34 523.007C1217.41 523.544 1216.27 523.544 1215.34 523.007L1176.31 500.46C1175.38 499.924 1174.81 498.934 1174.81 497.863V452.762C1174.81 451.691 1175.38 450.701 1176.31 450.165L1215.34 427.618Z" fill="#0F172A"/>
<path d="M1218.09 428.051L1257.11 450.598C1257.88 451.045 1258.37 451.869 1258.37 452.762V497.863C1258.37 498.756 1257.88 499.58 1257.11 500.027L1218.09 522.574C1217.32 523.022 1216.36 523.022 1215.59 522.574L1176.56 500.027C1175.79 499.58 1175.31 498.756 1175.31 497.863V452.762C1175.31 451.869 1175.79 451.045 1176.56 450.598L1215.59 428.051C1216.36 427.603 1217.32 427.603 1218.09 428.051Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1298.39 427.618C1299.32 427.081 1300.46 427.081 1301.39 427.618L1340.42 450.165C1341.35 450.701 1341.92 451.691 1341.92 452.762V497.863C1341.92 498.934 1341.35 499.924 1340.42 500.46L1301.39 523.007C1300.46 523.544 1299.32 523.544 1298.39 523.007L1259.37 500.46C1258.44 499.924 1257.86 498.934 1257.86 497.863V452.762C1257.86 451.691 1258.44 450.701 1259.37 450.165L1298.39 427.618Z" fill="#0F172A"/>
<path d="M1301.14 428.051L1340.17 450.598C1340.94 451.045 1341.42 451.869 1341.42 452.762V497.863C1341.42 498.756 1340.94 499.58 1340.17 500.027L1301.14 522.574C1300.37 523.022 1299.41 523.022 1298.64 522.574L1259.62 500.027C1258.85 499.58 1258.37 498.756 1258.37 497.863V452.762C1258.37 451.869 1258.85 451.045 1259.62 450.598L1298.64 428.051C1299.41 427.603 1300.37 427.603 1301.14 428.051Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1381.45 427.618C1382.38 427.081 1383.52 427.081 1384.45 427.618L1423.48 450.165C1424.41 450.701 1424.98 451.691 1424.98 452.762V497.863C1424.98 498.934 1424.41 499.924 1423.48 500.46L1384.45 523.007C1383.52 523.544 1382.38 523.544 1381.45 523.007L1342.42 500.46C1341.49 499.924 1340.92 498.934 1340.92 497.863V452.762C1340.92 451.691 1341.49 450.701 1342.42 450.165L1381.45 427.618Z" fill="#0F172A"/>
<path d="M1384.2 428.051L1423.23 450.598C1424 451.045 1424.48 451.869 1424.48 452.762V497.863C1424.48 498.756 1424 499.58 1423.23 500.027L1384.2 522.574C1383.43 523.022 1382.47 523.022 1381.7 522.574L1342.67 500.027C1341.9 499.58 1341.42 498.756 1341.42 497.863V452.762C1341.42 451.869 1341.9 451.045 1342.67 450.598L1381.7 428.051C1382.47 427.603 1383.43 427.603 1384.2 428.051Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1464.51 427.618C1465.44 427.081 1466.58 427.081 1467.51 427.618L1506.54 450.165C1507.47 450.701 1508.04 451.691 1508.04 452.762V497.863C1508.04 498.934 1507.47 499.924 1506.54 500.46L1467.51 523.007C1466.58 523.544 1465.44 523.544 1464.51 523.007L1425.48 500.46C1424.55 499.924 1423.98 498.934 1423.98 497.863V452.762C1423.98 451.691 1424.55 450.701 1425.48 450.165L1464.51 427.618Z" fill="#0F172A"/>
<path d="M1467.26 428.051L1506.29 450.598C1507.06 451.045 1507.54 451.869 1507.54 452.762V497.863C1507.54 498.756 1507.06 499.58 1506.29 500.027L1467.26 522.574C1466.49 523.022 1465.53 523.022 1464.76 522.574L1425.73 500.027C1424.96 499.58 1424.48 498.756 1424.48 497.863V452.762C1424.48 451.869 1424.96 451.045 1425.73 450.598L1464.76 428.051C1465.53 427.603 1466.49 427.603 1467.26 428.051Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M11.0057 498.743C11.9354 498.206 13.0812 498.206 14.0108 498.743L53.0376 521.29C53.9657 521.826 54.5372 522.816 54.5372 523.887V568.988C54.5372 570.059 53.9657 571.049 53.0376 571.585L14.0108 594.132C13.0812 594.669 11.9354 594.669 11.0057 594.132L-28.0211 571.585C-28.9491 571.049 -29.5206 570.059 -29.5206 568.988V523.887C-29.5206 522.816 -28.9491 521.826 -28.0211 521.29L11.0057 498.743Z" fill="#0F172A"/>
<path d="M13.7604 499.176L52.7873 521.723C53.5605 522.17 54.0368 522.994 54.0368 523.887V568.988C54.0368 569.881 53.5605 570.705 52.7873 571.152L13.7604 593.699C12.9857 594.147 12.0308 594.147 11.2561 593.699L-27.7707 571.152C-28.5441 570.705 -29.0203 569.881 -29.0203 568.988V523.887C-29.0203 522.994 -28.5441 522.17 -27.7707 521.723L11.2561 499.176C12.0308 498.728 12.9857 498.728 13.7604 499.176Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M94.0628 498.743C94.9925 498.206 96.1383 498.206 97.0679 498.743L136.095 521.29C137.023 521.826 137.594 522.816 137.594 523.887V568.988C137.594 570.059 137.023 571.049 136.095 571.585L97.0679 594.132C96.1383 594.669 94.9925 594.669 94.0628 594.132L55.036 571.585C54.108 571.049 53.5365 570.059 53.5365 568.988V523.887C53.5365 522.816 54.108 521.826 55.036 521.29L94.0628 498.743Z" fill="#0F172A"/>
<path d="M96.8175 499.176L135.844 521.723C136.618 522.17 137.094 522.994 137.094 523.887V568.988C137.094 569.881 136.618 570.705 135.844 571.152L96.8175 593.699C96.0428 594.147 95.0879 594.147 94.3132 593.699L55.2864 571.152C54.513 570.705 54.0368 569.881 54.0368 568.988V523.887C54.0368 522.994 54.513 522.17 55.2864 521.723L94.3132 499.176C95.0879 498.728 96.0428 498.728 96.8175 499.176Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M177.119 498.743C178.049 498.206 179.196 498.206 180.126 498.743L219.152 521.29C220.08 521.826 220.651 522.816 220.651 523.887V568.988C220.651 570.059 220.08 571.049 219.152 571.585L180.126 594.132C179.196 594.669 178.049 594.669 177.119 594.132L138.093 571.585C137.165 571.049 136.594 570.059 136.594 568.988V523.887C136.594 522.816 137.165 521.826 138.093 521.29L177.119 498.743Z" fill="#0F172A"/>
<path d="M179.874 499.176L218.901 521.723C219.675 522.17 220.151 522.994 220.151 523.887V568.988C220.151 569.881 219.675 570.705 218.901 571.152L179.874 593.699C179.1 594.147 178.145 594.147 177.371 593.699L138.344 571.152C137.57 570.705 137.094 569.881 137.094 568.988V523.887C137.094 522.994 137.57 522.17 138.344 521.723L177.371 499.176C178.145 498.728 179.1 498.728 179.874 499.176Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M260.177 498.743C261.106 498.206 262.253 498.206 263.183 498.743L302.21 521.29C303.137 521.826 303.709 522.816 303.709 523.887V568.988C303.709 570.059 303.137 571.049 302.21 571.585L263.183 594.132C262.253 594.669 261.106 594.669 260.177 594.132L221.15 571.585C220.222 571.049 219.651 570.059 219.651 568.988V523.887C219.651 522.816 220.222 521.826 221.15 521.29L260.177 498.743Z" fill="#0F172A"/>
<path d="M262.931 499.176L301.958 521.723C302.732 522.17 303.208 522.994 303.208 523.887V568.988C303.208 569.881 302.732 570.705 301.958 571.152L262.931 593.699C262.157 594.147 261.202 594.147 260.428 593.699L221.401 571.152C220.627 570.705 220.151 569.881 220.151 568.988V523.887C220.151 522.994 220.627 522.17 221.401 521.723L260.428 499.176C261.202 498.728 262.157 498.728 262.931 499.176Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M343.234 498.743C344.163 498.206 345.31 498.206 346.24 498.743L385.267 521.29C386.194 521.826 386.766 522.816 386.766 523.887V568.988C386.766 570.059 386.194 571.049 385.267 571.585L346.24 594.132C345.31 594.669 344.163 594.669 343.234 594.132L304.207 571.585C303.279 571.049 302.708 570.059 302.708 568.988V523.887C302.708 522.816 303.279 521.826 304.207 521.29L343.234 498.743Z" fill="#0F172A"/>
<path d="M345.989 499.176L385.015 521.723C385.789 522.17 386.265 522.994 386.265 523.887V568.988C386.265 569.881 385.789 570.705 385.015 571.152L345.989 593.699C345.214 594.147 344.259 594.147 343.485 593.699L304.458 571.152C303.685 570.705 303.208 569.881 303.208 568.988V523.887C303.208 522.994 303.685 522.17 304.458 521.723L343.485 499.176C344.259 498.728 345.214 498.728 345.989 499.176Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M426.291 498.743C427.22 498.206 428.367 498.206 429.297 498.743L468.324 521.29C469.251 521.826 469.823 522.816 469.823 523.887V568.988C469.823 570.059 469.251 571.049 468.324 571.585L429.297 594.132C428.367 594.669 427.22 594.669 426.291 594.132L387.264 571.585C386.336 571.049 385.765 570.059 385.765 568.988V523.887C385.765 522.816 386.336 521.826 387.264 521.29L426.291 498.743Z" fill="#0F172A"/>
<path d="M429.046 499.176L468.073 521.723C468.846 522.17 469.322 522.994 469.322 523.887V568.988C469.322 569.881 468.846 570.705 468.073 571.152L429.046 593.699C428.271 594.147 427.317 594.147 426.542 593.699L387.515 571.152C386.742 570.705 386.265 569.881 386.265 568.988V523.887C386.265 522.994 386.742 522.17 387.515 521.723L426.542 499.176C427.317 498.728 428.271 498.728 429.046 499.176Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M509.348 498.743C510.278 498.206 511.424 498.206 512.354 498.743L551.381 521.29C552.309 521.826 552.88 522.816 552.88 523.887V568.988C552.88 570.059 552.309 571.049 551.381 571.585L512.354 594.132C511.424 594.669 510.278 594.669 509.348 594.132L470.321 571.585C469.393 571.049 468.822 570.059 468.822 568.988V523.887C468.822 522.816 469.393 521.826 470.321 521.29L509.348 498.743Z" fill="#0F172A"/>
<path d="M512.103 499.176L551.13 521.723C551.903 522.17 552.38 522.994 552.38 523.887V568.988C552.38 569.881 551.903 570.705 551.13 571.152L512.103 593.699C511.328 594.147 510.374 594.147 509.599 593.699L470.572 571.152C469.799 570.705 469.322 569.881 469.322 568.988V523.887C469.322 522.994 469.799 522.17 470.572 521.723L509.599 499.176C510.374 498.728 511.328 498.728 512.103 499.176Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M592.405 498.743C593.335 498.206 594.482 498.206 595.411 498.743L634.438 521.29C635.366 521.826 635.937 522.816 635.937 523.887V568.988C635.937 570.059 635.366 571.049 634.438 571.585L595.411 594.132C594.482 594.669 593.335 594.669 592.405 594.132L553.378 571.585C552.451 571.049 551.879 570.059 551.879 568.988V523.887C551.879 522.816 552.451 521.826 553.378 521.29L592.405 498.743Z" fill="#0F172A"/>
<path d="M595.16 499.176L634.187 521.723C634.96 522.17 635.437 522.994 635.437 523.887V568.988C635.437 569.881 634.96 570.705 634.187 571.152L595.16 593.699C594.385 594.147 593.431 594.147 592.656 593.699L553.629 571.152C552.856 570.705 552.38 569.881 552.38 568.988V523.887C552.38 522.994 552.856 522.17 553.629 521.723L592.656 499.176C593.431 498.728 594.385 498.728 595.16 499.176Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M675.462 498.743C676.392 498.206 677.539 498.206 678.468 498.743L717.495 521.29C718.423 521.826 718.994 522.816 718.994 523.887V568.988C718.994 570.059 718.423 571.049 717.495 571.585L678.468 594.132C677.539 594.669 676.392 594.669 675.462 594.132L636.435 571.585C635.508 571.049 634.936 570.059 634.936 568.988V523.887C634.936 522.816 635.508 521.826 636.435 521.29L675.462 498.743Z" fill="#0F172A"/>
<path d="M678.217 499.176L717.244 521.723C718.017 522.17 718.494 522.994 718.494 523.887V568.988C718.494 569.881 718.017 570.705 717.244 571.152L678.217 593.699C677.443 594.147 676.488 594.147 675.713 593.699L636.687 571.152C635.913 570.705 635.437 569.881 635.437 568.988V523.887C635.437 522.994 635.913 522.17 636.687 521.723L675.713 499.176C676.488 498.728 677.443 498.728 678.217 499.176Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M758.519 498.743C759.449 498.206 760.596 498.206 761.525 498.743L800.552 521.29C801.48 521.826 802.051 522.816 802.051 523.887V568.988C802.051 570.059 801.48 571.049 800.552 571.585L761.525 594.132C760.596 594.669 759.449 594.669 758.519 594.132L719.492 571.585C718.565 571.049 717.993 570.059 717.993 568.988V523.887C717.993 522.816 718.565 521.826 719.492 521.29L758.519 498.743Z" fill="#0F172A"/>
<path d="M761.274 499.176L800.301 521.723C801.075 522.17 801.551 522.994 801.551 523.887V568.988C801.551 569.881 801.075 570.705 800.301 571.152L761.274 593.699C760.5 594.147 759.545 594.147 758.771 593.699L719.744 571.152C718.97 570.705 718.494 569.881 718.494 568.988V523.887C718.494 522.994 718.97 522.17 719.744 521.723L758.771 499.176C759.545 498.728 760.5 498.728 761.274 499.176Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M841.576 498.743C842.506 498.206 843.653 498.206 844.583 498.743L883.609 521.29C884.537 521.826 885.108 522.816 885.108 523.887V568.988C885.108 570.059 884.537 571.049 883.609 571.585L844.583 594.132C843.653 594.669 842.506 594.669 841.576 594.132L802.55 571.585C801.622 571.049 801.051 570.059 801.051 568.988V523.887C801.051 522.816 801.622 521.826 802.55 521.29L841.576 498.743Z" fill="#0F172A"/>
<path d="M844.331 499.176L883.358 521.723C884.132 522.17 884.608 522.994 884.608 523.887V568.988C884.608 569.881 884.132 570.705 883.358 571.152L844.331 593.699C843.557 594.147 842.602 594.147 841.828 593.699L802.801 571.152C802.027 570.705 801.551 569.881 801.551 568.988V523.887C801.551 522.994 802.027 522.17 802.801 521.723L841.828 499.176C842.602 498.728 843.557 498.728 844.331 499.176Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M924.634 498.743C925.563 498.206 926.71 498.206 927.64 498.743L966.666 521.29C967.594 521.826 968.165 522.816 968.165 523.887V568.988C968.165 570.059 967.594 571.049 966.666 571.585L927.64 594.132C926.71 594.669 925.563 594.669 924.634 594.132L885.607 571.585C884.679 571.049 884.108 570.059 884.108 568.988V523.887C884.108 522.816 884.679 521.826 885.607 521.29L924.634 498.743Z" fill="#0F172A"/>
<path d="M927.388 499.176L966.415 521.723C967.189 522.17 967.665 522.994 967.665 523.887V568.988C967.665 569.881 967.189 570.705 966.415 571.152L927.388 593.699C926.614 594.147 925.659 594.147 924.885 593.699L885.858 571.152C885.084 570.705 884.608 569.881 884.608 568.988V523.887C884.608 522.994 885.084 522.17 885.858 521.723L924.885 499.176C925.659 498.728 926.614 498.728 927.388 499.176Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1007.69 498.743C1008.62 498.206 1009.76 498.206 1010.69 498.743L1049.72 521.29C1050.65 521.826 1051.22 522.816 1051.22 523.887V568.988C1051.22 570.059 1050.65 571.049 1049.72 571.585L1010.69 594.132C1009.76 594.669 1008.62 594.669 1007.69 594.132L968.664 571.585C967.736 571.049 967.165 570.059 967.165 568.988V523.887C967.165 522.816 967.736 521.826 968.664 521.29L1007.69 498.743Z" fill="#0F172A"/>
<path d="M1010.44 499.176L1049.47 521.723C1050.24 522.17 1050.72 522.994 1050.72 523.887V568.988C1050.72 569.881 1050.24 570.705 1049.47 571.152L1010.44 593.699C1009.67 594.147 1008.71 594.147 1007.94 593.699L968.915 571.152C968.142 570.705 967.665 569.881 967.665 568.988V523.887C967.665 522.994 968.142 522.17 968.915 521.723L1007.94 499.176C1008.71 498.728 1009.67 498.728 1010.44 499.176Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1090.75 498.743C1091.68 498.206 1092.82 498.206 1093.75 498.743L1132.78 521.29C1133.71 521.826 1134.28 522.816 1134.28 523.887V568.988C1134.28 570.059 1133.71 571.049 1132.78 571.585L1093.75 594.132C1092.82 594.669 1091.68 594.669 1090.75 594.132L1051.72 571.585C1050.79 571.049 1050.22 570.059 1050.22 568.988V523.887C1050.22 522.816 1050.79 521.826 1051.72 521.29L1090.75 498.743Z" fill="#0F172A"/>
<path d="M1093.5 499.176L1132.53 521.723C1133.3 522.17 1133.78 522.994 1133.78 523.887V568.988C1133.78 569.881 1133.3 570.705 1132.53 571.152L1093.5 593.699C1092.73 594.147 1091.77 594.147 1091 593.699L1051.97 571.152C1051.2 570.705 1050.72 569.881 1050.72 568.988V523.887C1050.72 522.994 1051.2 522.17 1051.97 521.723L1091 499.176C1091.77 498.728 1092.73 498.728 1093.5 499.176Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1173.81 498.743C1174.74 498.206 1175.88 498.206 1176.81 498.743L1215.84 521.29C1216.77 521.826 1217.34 522.816 1217.34 523.887V568.988C1217.34 570.059 1216.77 571.049 1215.84 571.585L1176.81 594.132C1175.88 594.669 1174.74 594.669 1173.81 594.132L1134.78 571.585C1133.85 571.049 1133.28 570.059 1133.28 568.988V523.887C1133.28 522.816 1133.85 521.826 1134.78 521.29L1173.81 498.743Z" fill="#0F172A"/>
<path d="M1176.56 499.176L1215.59 521.723C1216.36 522.17 1216.84 522.994 1216.84 523.887V568.988C1216.84 569.881 1216.36 570.705 1215.59 571.152L1176.56 593.699C1175.79 594.147 1174.83 594.147 1174.06 593.699L1135.03 571.152C1134.26 570.705 1133.78 569.881 1133.78 568.988V523.887C1133.78 522.994 1134.26 522.17 1135.03 521.723L1174.06 499.176C1174.83 498.728 1175.79 498.728 1176.56 499.176Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1256.86 498.743C1257.79 498.206 1258.94 498.206 1259.87 498.743L1298.89 521.29C1299.82 521.826 1300.39 522.816 1300.39 523.887V568.988C1300.39 570.059 1299.82 571.049 1298.89 571.585L1259.87 594.132C1258.94 594.669 1257.79 594.669 1256.86 594.132L1217.84 571.585C1216.91 571.049 1216.34 570.059 1216.34 568.988V523.887C1216.34 522.816 1216.91 521.826 1217.84 521.29L1256.86 498.743Z" fill="#0F172A"/>
<path d="M1259.62 499.176L1298.64 521.723C1299.41 522.17 1299.89 522.994 1299.89 523.887V568.988C1299.89 569.881 1299.41 570.705 1298.64 571.152L1259.62 593.699C1258.85 594.147 1257.88 594.147 1257.11 593.699L1218.09 571.152C1217.32 570.705 1216.84 569.881 1216.84 568.988V523.887C1216.84 522.994 1217.32 522.17 1218.09 521.723L1257.11 499.176C1257.88 498.728 1258.85 498.728 1259.62 499.176Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1339.92 498.743C1340.85 498.206 1341.99 498.206 1342.92 498.743L1381.95 521.29C1382.88 521.826 1383.45 522.816 1383.45 523.887V568.988C1383.45 570.059 1382.88 571.049 1381.95 571.585L1342.92 594.132C1341.99 594.669 1340.85 594.669 1339.92 594.132L1300.89 571.585C1299.96 571.049 1299.39 570.059 1299.39 568.988V523.887C1299.39 522.816 1299.96 521.826 1300.89 521.29L1339.92 498.743Z" fill="#0F172A"/>
<path d="M1342.67 499.176L1381.7 521.723C1382.47 522.17 1382.95 522.994 1382.95 523.887V568.988C1382.95 569.881 1382.47 570.705 1381.7 571.152L1342.67 593.699C1341.9 594.147 1340.94 594.147 1340.17 593.699L1301.14 571.152C1300.37 570.705 1299.89 569.881 1299.89 568.988V523.887C1299.89 522.994 1300.37 522.17 1301.14 521.723L1340.17 499.176C1340.94 498.728 1341.9 498.728 1342.67 499.176Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1422.98 498.743C1423.91 498.206 1425.05 498.206 1425.98 498.743L1465.01 521.29C1465.94 521.826 1466.51 522.816 1466.51 523.887V568.988C1466.51 570.059 1465.94 571.049 1465.01 571.585L1425.98 594.132C1425.05 594.669 1423.91 594.669 1422.98 594.132L1383.95 571.585C1383.02 571.049 1382.45 570.059 1382.45 568.988V523.887C1382.45 522.816 1383.02 521.826 1383.95 521.29L1422.98 498.743Z" fill="#0F172A"/>
<path d="M1425.73 499.176L1464.76 521.723C1465.53 522.17 1466.01 522.994 1466.01 523.887V568.988C1466.01 569.881 1465.53 570.705 1464.76 571.152L1425.73 593.699C1424.96 594.147 1424 594.147 1423.23 593.699L1384.2 571.152C1383.43 570.705 1382.95 569.881 1382.95 568.988V523.887C1382.95 522.994 1383.43 522.17 1384.2 521.723L1423.23 499.176C1424 498.728 1424.96 498.728 1425.73 499.176Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M52.5343 569.868C53.4639 569.331 54.6097 569.331 55.5393 569.868L94.5662 592.415C95.4942 592.951 96.0657 593.941 96.0657 595.012V640.113C96.0657 641.184 95.4942 642.174 94.5662 642.71L55.5393 665.257C54.6097 665.794 53.4639 665.794 52.5343 665.257L13.5074 642.71C12.5794 642.174 12.0079 641.184 12.0079 640.113V595.012C12.0079 593.941 12.5794 592.951 13.5074 592.415L52.5343 569.868Z" fill="#0F172A"/>
<path d="M55.289 570.301L94.3158 592.848C95.089 593.295 95.5654 594.119 95.5654 595.012V640.113C95.5654 641.006 95.089 641.83 94.3158 642.277L55.289 664.824C54.5142 665.272 53.5594 665.272 52.7846 664.824L13.7578 642.277C12.9845 641.83 12.5082 641.006 12.5082 640.113V595.012C12.5082 594.119 12.9845 593.295 13.7578 592.848L52.7846 570.301C53.5594 569.853 54.5142 569.853 55.289 570.301Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M135.591 569.868C136.521 569.331 137.667 569.331 138.597 569.868L177.624 592.415C178.551 592.951 179.123 593.941 179.123 595.012V640.113C179.123 641.184 178.551 642.174 177.624 642.71L138.597 665.257C137.667 665.794 136.521 665.794 135.591 665.257L96.5646 642.71C95.6365 642.174 95.065 641.184 95.065 640.113V595.012C95.065 593.941 95.6365 592.951 96.5646 592.415L135.591 569.868Z" fill="#0F172A"/>
<path d="M138.346 570.301L177.373 592.848C178.146 593.295 178.623 594.119 178.623 595.012V640.113C178.623 641.006 178.146 641.83 177.373 642.277L138.346 664.824C137.571 665.272 136.617 665.272 135.842 664.824L96.8149 642.277C96.0416 641.83 95.5654 641.006 95.5654 640.113V595.012C95.5654 594.119 96.0416 593.295 96.8149 592.848L135.842 570.301C136.617 569.853 137.571 569.853 138.346 570.301Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M218.648 569.868C219.578 569.331 220.724 569.331 221.654 569.868L260.681 592.415C261.609 592.951 262.18 593.941 262.18 595.012V640.113C262.18 641.184 261.609 642.174 260.681 642.71L221.654 665.257C220.724 665.794 219.578 665.794 218.648 665.257L179.621 642.71C178.694 642.174 178.122 641.184 178.122 640.113V595.012C178.122 593.941 178.694 592.951 179.621 592.415L218.648 569.868Z" fill="#0F172A"/>
<path d="M221.403 570.301L260.43 592.848C261.203 593.295 261.68 594.119 261.68 595.012V640.113C261.68 641.006 261.203 641.83 260.43 642.277L221.403 664.824C220.628 665.272 219.674 665.272 218.899 664.824L179.872 642.277C179.099 641.83 178.622 641.006 178.622 640.113V595.012C178.622 594.119 179.099 593.295 179.872 592.848L218.899 570.301C219.674 569.853 220.628 569.853 221.403 570.301Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M301.705 569.868C302.635 569.331 303.782 569.331 304.711 569.868L343.738 592.415C344.666 592.951 345.237 593.941 345.237 595.012V640.113C345.237 641.184 344.666 642.174 343.738 642.71L304.711 665.257C303.782 665.794 302.635 665.794 301.705 665.257L262.678 642.71C261.751 642.174 261.179 641.184 261.179 640.113V595.012C261.179 593.941 261.751 592.951 262.678 592.415L301.705 569.868Z" fill="#0F172A"/>
<path d="M304.46 570.301L343.487 592.848C344.26 593.295 344.737 594.119 344.737 595.012V640.113C344.737 641.006 344.26 641.83 343.487 642.277L304.46 664.824C303.686 665.272 302.731 665.272 301.956 664.824L262.929 642.277C262.156 641.83 261.68 641.006 261.68 640.113V595.012C261.68 594.119 262.156 593.295 262.929 592.848L301.956 570.301C302.731 569.853 303.686 569.853 304.46 570.301Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M384.762 569.868C385.692 569.331 386.839 569.331 387.768 569.868L426.795 592.415C427.723 592.951 428.294 593.941 428.294 595.012V640.113C428.294 641.184 427.723 642.174 426.795 642.71L387.768 665.257C386.839 665.794 385.692 665.794 384.762 665.257L345.735 642.71C344.808 642.174 344.236 641.184 344.236 640.113V595.012C344.236 593.941 344.808 592.951 345.735 592.415L384.762 569.868Z" fill="#0F172A"/>
<path d="M387.517 570.301L426.544 592.848C427.318 593.295 427.794 594.119 427.794 595.012V640.113C427.794 641.006 427.318 641.83 426.544 642.277L387.517 664.824C386.743 665.272 385.788 665.272 385.013 664.824L345.987 642.277C345.213 641.83 344.737 641.006 344.737 640.113V595.012C344.737 594.119 345.213 593.295 345.987 592.848L385.013 570.301C385.788 569.853 386.743 569.853 387.517 570.301Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M467.819 569.868C468.749 569.331 469.896 569.331 470.825 569.868L509.852 592.415C510.78 592.951 511.351 593.941 511.351 595.012V640.113C511.351 641.184 510.78 642.174 509.852 642.71L470.825 665.257C469.896 665.794 468.749 665.794 467.819 665.257L428.793 642.71C427.865 642.174 427.294 641.184 427.294 640.113V595.012C427.294 593.941 427.865 592.951 428.793 592.415L467.819 569.868Z" fill="#0F172A"/>
<path d="M470.574 570.301L509.601 592.848C510.375 593.295 510.851 594.119 510.851 595.012V640.113C510.851 641.006 510.375 641.83 509.601 642.277L470.574 664.824C469.8 665.272 468.845 665.272 468.071 664.824L429.044 642.277C428.27 641.83 427.794 641.006 427.794 640.113V595.012C427.794 594.119 428.27 593.295 429.044 592.848L468.071 570.301C468.845 569.853 469.8 569.853 470.574 570.301Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M550.877 569.868C551.806 569.331 552.953 569.331 553.883 569.868L592.909 592.415C593.837 592.951 594.408 593.941 594.408 595.012V640.113C594.408 641.184 593.837 642.174 592.909 642.71L553.883 665.257C552.953 665.794 551.806 665.794 550.877 665.257L511.85 642.71C510.922 642.174 510.351 641.184 510.351 640.113V595.012C510.351 593.941 510.922 592.951 511.85 592.415L550.877 569.868Z" fill="#0F172A"/>
<path d="M553.631 570.301L592.658 592.848C593.432 593.295 593.908 594.119 593.908 595.012V640.113C593.908 641.006 593.432 641.83 592.658 642.277L553.631 664.824C552.857 665.272 551.902 665.272 551.128 664.824L512.101 642.277C511.327 641.83 510.851 641.006 510.851 640.113V595.012C510.851 594.119 511.327 593.295 512.101 592.848L551.128 570.301C551.902 569.853 552.857 569.853 553.631 570.301Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M633.934 569.868C634.863 569.331 636.01 569.331 636.94 569.868L675.967 592.415C676.894 592.951 677.466 593.941 677.466 595.012V640.113C677.466 641.184 676.894 642.174 675.967 642.71L636.94 665.257C636.01 665.794 634.863 665.794 633.934 665.257L594.907 642.71C593.979 642.174 593.408 641.184 593.408 640.113V595.012C593.408 593.941 593.979 592.951 594.907 592.415L633.934 569.868Z" fill="#0F172A"/>
<path d="M636.689 570.301L675.715 592.848C676.489 593.295 676.965 594.119 676.965 595.012V640.113C676.965 641.006 676.489 641.83 675.715 642.277L636.689 664.824C635.914 665.272 634.959 665.272 634.185 664.824L595.158 642.277C594.384 641.83 593.908 641.006 593.908 640.113V595.012C593.908 594.119 594.384 593.295 595.158 592.848L634.185 570.301C634.959 569.853 635.914 569.853 636.689 570.301Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M716.991 569.868C717.92 569.331 719.067 569.331 719.997 569.868L759.024 592.415C759.951 592.951 760.523 593.941 760.523 595.012V640.113C760.523 641.184 759.951 642.174 759.024 642.71L719.997 665.257C719.067 665.794 717.92 665.794 716.991 665.257L677.964 642.71C677.036 642.174 676.465 641.184 676.465 640.113V595.012C676.465 593.941 677.036 592.951 677.964 592.415L716.991 569.868Z" fill="#0F172A"/>
<path d="M719.746 570.301L758.772 592.848C759.546 593.295 760.022 594.119 760.022 595.012V640.113C760.022 641.006 759.546 641.83 758.772 642.277L719.746 664.824C718.971 665.272 718.016 665.272 717.242 664.824L678.215 642.277C677.442 641.83 676.965 641.006 676.965 640.113V595.012C676.965 594.119 677.442 593.295 678.215 592.848L717.242 570.301C718.016 569.853 718.971 569.853 719.746 570.301Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M800.048 569.868C800.977 569.331 802.124 569.331 803.054 569.868L842.081 592.415C843.008 592.951 843.58 593.941 843.58 595.012V640.113C843.58 641.184 843.008 642.174 842.081 642.71L803.054 665.257C802.124 665.794 800.977 665.794 800.048 665.257L761.021 642.71C760.093 642.174 759.522 641.184 759.522 640.113V595.012C759.522 593.941 760.093 592.951 761.021 592.415L800.048 569.868Z" fill="#0F172A"/>
<path d="M802.803 570.301L841.83 592.848C842.603 593.295 843.079 594.119 843.079 595.012V640.113C843.079 641.006 842.603 641.83 841.83 642.277L802.803 664.824C802.028 665.272 801.074 665.272 800.299 664.824L761.272 642.277C760.499 641.83 760.022 641.006 760.022 640.113V595.012C760.022 594.119 760.499 593.295 761.272 592.848L800.299 570.301C801.074 569.853 802.028 569.853 802.803 570.301Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M883.105 569.868C884.035 569.331 885.181 569.331 886.111 569.868L925.138 592.415C926.066 592.951 926.637 593.941 926.637 595.012V640.113C926.637 641.184 926.066 642.174 925.138 642.71L886.111 665.257C885.181 665.794 884.035 665.794 883.105 665.257L844.078 642.71C843.15 642.174 842.579 641.184 842.579 640.113V595.012C842.579 593.941 843.15 592.951 844.078 592.415L883.105 569.868Z" fill="#0F172A"/>
<path d="M885.86 570.301L924.887 592.848C925.66 593.295 926.137 594.119 926.137 595.012V640.113C926.137 641.006 925.66 641.83 924.887 642.277L885.86 664.824C885.085 665.272 884.131 665.272 883.356 664.824L844.329 642.277C843.556 641.83 843.079 641.006 843.079 640.113V595.012C843.079 594.119 843.556 593.295 844.329 592.848L883.356 570.301C884.131 569.853 885.085 569.853 885.86 570.301Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M966.162 569.868C967.092 569.331 968.239 569.331 969.168 569.868L1008.19 592.415C1009.12 592.951 1009.69 593.941 1009.69 595.012V640.113C1009.69 641.184 1009.12 642.174 1008.19 642.71L969.168 665.257C968.239 665.794 967.092 665.794 966.162 665.257L927.135 642.71C926.208 642.174 925.636 641.184 925.636 640.113V595.012C925.636 593.941 926.208 592.951 927.135 592.415L966.162 569.868Z" fill="#0F172A"/>
<path d="M968.917 570.301L1007.94 592.848C1008.71 593.295 1009.19 594.119 1009.19 595.012V640.113C1009.19 641.006 1008.71 641.83 1007.94 642.277L968.917 664.824C968.142 665.272 967.188 665.272 966.413 664.824L927.386 642.277C926.613 641.83 926.137 641.006 926.137 640.113V595.012C926.137 594.119 926.613 593.295 927.386 592.848L966.413 570.301C967.188 569.853 968.142 569.853 968.917 570.301Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1049.22 569.868C1050.15 569.331 1051.29 569.331 1052.22 569.868L1091.25 592.415C1092.18 592.951 1092.75 593.941 1092.75 595.012V640.113C1092.75 641.184 1092.18 642.174 1091.25 642.71L1052.22 665.257C1051.29 665.794 1050.15 665.794 1049.22 665.257L1010.19 642.71C1009.26 642.174 1008.69 641.184 1008.69 640.113V595.012C1008.69 593.941 1009.26 592.951 1010.19 592.415L1049.22 569.868Z" fill="#0F172A"/>
<path d="M1051.97 570.301L1091 592.848C1091.77 593.295 1092.25 594.119 1092.25 595.012V640.113C1092.25 641.006 1091.77 641.83 1091 642.277L1051.97 664.824C1051.2 665.272 1050.24 665.272 1049.47 664.824L1010.44 642.277C1009.67 641.83 1009.19 641.006 1009.19 640.113V595.012C1009.19 594.119 1009.67 593.295 1010.44 592.848L1049.47 570.301C1050.24 569.853 1051.2 569.853 1051.97 570.301Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1132.28 569.868C1133.21 569.331 1134.35 569.331 1135.28 569.868L1174.31 592.415C1175.24 592.951 1175.81 593.941 1175.81 595.012V640.113C1175.81 641.184 1175.24 642.174 1174.31 642.71L1135.28 665.257C1134.35 665.794 1133.21 665.794 1132.28 665.257L1093.25 642.71C1092.32 642.174 1091.75 641.184 1091.75 640.113V595.012C1091.75 593.941 1092.32 592.951 1093.25 592.415L1132.28 569.868Z" fill="#0F172A"/>
<path d="M1135.03 570.301L1174.06 592.848C1174.83 593.295 1175.31 594.119 1175.31 595.012V640.113C1175.31 641.006 1174.83 641.83 1174.06 642.277L1135.03 664.824C1134.26 665.272 1133.3 665.272 1132.53 664.824L1093.5 642.277C1092.73 641.83 1092.25 641.006 1092.25 640.113V595.012C1092.25 594.119 1092.73 593.295 1093.5 592.848L1132.53 570.301C1133.3 569.853 1134.26 569.853 1135.03 570.301Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1215.34 569.868C1216.27 569.331 1217.41 569.331 1218.34 569.868L1257.36 592.415C1258.3 592.951 1258.87 593.941 1258.87 595.012V640.113C1258.87 641.184 1258.3 642.174 1257.36 642.71L1218.34 665.257C1217.41 665.794 1216.27 665.794 1215.34 665.257L1176.31 642.71C1175.38 642.174 1174.81 641.184 1174.81 640.113V595.012C1174.81 593.941 1175.38 592.951 1176.31 592.415L1215.34 569.868Z" fill="#0F172A"/>
<path d="M1218.09 570.301L1257.11 592.848C1257.88 593.295 1258.37 594.119 1258.37 595.012V640.113C1258.37 641.006 1257.88 641.83 1257.11 642.277L1218.09 664.824C1217.32 665.272 1216.36 665.272 1215.59 664.824L1176.56 642.277C1175.79 641.83 1175.31 641.006 1175.31 640.113V595.012C1175.31 594.119 1175.79 593.295 1176.56 592.848L1215.59 570.301C1216.36 569.853 1217.32 569.853 1218.09 570.301Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1298.39 569.868C1299.32 569.331 1300.46 569.331 1301.39 569.868L1340.42 592.415C1341.35 592.951 1341.92 593.941 1341.92 595.012V640.113C1341.92 641.184 1341.35 642.174 1340.42 642.71L1301.39 665.257C1300.46 665.794 1299.32 665.794 1298.39 665.257L1259.37 642.71C1258.44 642.174 1257.86 641.184 1257.86 640.113V595.012C1257.86 593.941 1258.44 592.951 1259.37 592.415L1298.39 569.868Z" fill="#0F172A"/>
<path d="M1301.14 570.301L1340.17 592.848C1340.94 593.295 1341.42 594.119 1341.42 595.012V640.113C1341.42 641.006 1340.94 641.83 1340.17 642.277L1301.14 664.824C1300.37 665.272 1299.41 665.272 1298.64 664.824L1259.62 642.277C1258.85 641.83 1258.37 641.006 1258.37 640.113V595.012C1258.37 594.119 1258.85 593.295 1259.62 592.848L1298.64 570.301C1299.41 569.853 1300.37 569.853 1301.14 570.301Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1381.45 569.868C1382.38 569.331 1383.52 569.331 1384.45 569.868L1423.48 592.415C1424.41 592.951 1424.98 593.941 1424.98 595.012V640.113C1424.98 641.184 1424.41 642.174 1423.48 642.71L1384.45 665.257C1383.52 665.794 1382.38 665.794 1381.45 665.257L1342.42 642.71C1341.49 642.174 1340.92 641.184 1340.92 640.113V595.012C1340.92 593.941 1341.49 592.951 1342.42 592.415L1381.45 569.868Z" fill="#0F172A"/>
<path d="M1384.2 570.301L1423.23 592.848C1424 593.295 1424.48 594.119 1424.48 595.012V640.113C1424.48 641.006 1424 641.83 1423.23 642.277L1384.2 664.824C1383.43 665.272 1382.47 665.272 1381.7 664.824L1342.67 642.277C1341.9 641.83 1341.42 641.006 1341.42 640.113V595.012C1341.42 594.119 1341.9 593.295 1342.67 592.848L1381.7 570.301C1382.47 569.853 1383.43 569.853 1384.2 570.301Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M1464.51 569.868C1465.44 569.331 1466.58 569.331 1467.51 569.868L1506.54 592.415C1507.47 592.951 1508.04 593.941 1508.04 595.012V640.113C1508.04 641.184 1507.47 642.174 1506.54 642.71L1467.51 665.257C1466.58 665.794 1465.44 665.794 1464.51 665.257L1425.48 642.71C1424.55 642.174 1423.98 641.184 1423.98 640.113V595.012C1423.98 593.941 1424.55 592.951 1425.48 592.415L1464.51 569.868Z" fill="#0F172A"/>
<path d="M1467.26 570.301L1506.29 592.848C1507.06 593.295 1507.54 594.119 1507.54 595.012V640.113C1507.54 641.006 1507.06 641.83 1506.29 642.277L1467.26 664.824C1466.49 665.272 1465.53 665.272 1464.76 664.824L1425.73 642.277C1424.96 641.83 1424.48 641.006 1424.48 640.113V595.012C1424.48 594.119 1424.96 593.295 1425.73 592.848L1464.76 570.301C1465.53 569.853 1466.49 569.853 1467.26 570.301Z" stroke="#4C1D95" stroke-opacity="0.3"/>
<path d="M-31.5235 283.403C-30.5939 282.866 -29.4481 282.866 -28.5185 283.403L10.5084 305.95C11.4364 306.486 12.0079 307.476 12.0079 308.547V353.648C12.0079 354.719 11.4364 355.709 10.5084 356.245L-28.5185 378.792C-29.4481 379.329 -30.5939 379.329 -31.5235 378.792L-70.5504 356.245C-71.4784 355.709 -72.0499 354.719 -72.0499 353.648V308.547C-72.0499 307.476 -71.4784 306.486 -70.5504 305.95L-31.5235 283.403Z" fill="#0F172A"/>
<path d="M-31.508 145.405C-30.5785 144.896 -29.4312 144.894 -28.5007 145.402L10.5626 166.728C11.4911 167.235 12.0637 168.173 12.0647 169.188L12.0647 212.5C12 214.5 12.5 214 11 214.5L-28.4101 235.791C-29.3396 236.301 -30.4868 236.302 -31.4173 235.794L-70.4802 214.468C-71.4091 213.961 -71.9818 213.023 -71.9828 212.009L-72.0256 169.272C-72.0266 168.257 -71.4559 167.318 -70.528 166.81L-31.508 145.405Z" fill="#0F172A"/>
<path d="M-31.5235 -1.58883C-30.5939 -2.13706 -29.4481 -2.13706 -28.5185 -1.58883L10.5084 21.4268C11.4364 21.9741 12.0079 22.9846 12.0079 24.078V70.1165C12.0079 71.2099 11.4364 72.2203 10.5084 72.7677L-28.5185 95.7833C-29.4481 96.3315 -30.5939 96.3315 -31.5235 95.7833L-70.5504 72.7677C-71.4784 72.2203 -72.0499 71.2099 -72.0499 70.1165V24.078C-72.0499 22.9846 -71.4784 21.9741 -70.5504 21.4268L-31.5235 -1.58883Z" fill="#0F172A"/>
</g>
</g>
<defs>
<linearGradient id="paint0_linear_3_341" x1="727.5" y1="0" x2="727.5" y2="630" gradientUnits="userSpaceOnUse">
<stop stop-color="#2563EB"/>
<stop offset="0.706076" stop-color="#6246EC" stop-opacity="0"/>
<stop offset="1" stop-color="#7C3AED" stop-opacity="0"/>
</linearGradient>
<clipPath id="clip0_3_341">
<rect width="1454" height="630" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 135 KiB

View File

@ -1,176 +0,0 @@
<svg width="1453" height="592" viewBox="0 0 1453 592" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_6907_192562" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="-38" width="1453" height="630">
<rect y="-38" width="1453" height="630" fill="url(#paint0_linear_6907_192562)"/>
</mask>
<g mask="url(#mask0_6907_192562)">
<path d="M52.2487 -36.6991C53.0229 -37.1466 53.9771 -37.1466 54.7513 -36.6991L93.7513 -14.1522C94.524 -13.7054 95 -12.8805 95 -11.9879V33.1129C95 34.0055 94.524 34.8304 93.7513 35.2772L54.7513 57.8241C53.9771 58.2716 53.0229 58.2716 52.2487 57.8241L13.2487 35.2772C12.4759 34.8304 12 34.0055 12 33.1129V-11.9879C12 -12.8805 12.4759 -13.7054 13.2487 -14.1522L52.2487 -36.6991Z" fill="white" stroke="#EDE9FE"/>
<path d="M135.249 -36.6991C136.023 -37.1466 136.977 -37.1466 137.751 -36.6991L176.751 -14.1522C177.524 -13.7054 178 -12.8805 178 -11.9879V33.1129C178 34.0055 177.524 34.8304 176.751 35.2772L137.751 57.8241C136.977 58.2716 136.023 58.2716 135.249 57.8241L96.2487 35.2772C95.4759 34.8304 95 34.0055 95 33.1129V-11.9879C95 -12.8805 95.4759 -13.7054 96.2487 -14.1522L135.249 -36.6991Z" fill="white" stroke="#EDE9FE"/>
<path d="M218.249 -36.6991C219.023 -37.1466 219.977 -37.1466 220.751 -36.6991L259.751 -14.1522C260.524 -13.7054 261 -12.8805 261 -11.9879V33.1129C261 34.0055 260.524 34.8304 259.751 35.2772L220.751 57.8241C219.977 58.2716 219.023 58.2716 218.249 57.8241L179.249 35.2772C178.476 34.8304 178 34.0055 178 33.1129V-11.9879C178 -12.8805 178.476 -13.7054 179.249 -14.1522L218.249 -36.6991Z" fill="white" stroke="#EDE9FE"/>
<path d="M301.249 -36.6991C302.023 -37.1466 302.977 -37.1466 303.751 -36.6991L342.751 -14.1522C343.524 -13.7054 344 -12.8805 344 -11.9879V33.1129C344 34.0055 343.524 34.8304 342.751 35.2772L303.751 57.8241C302.977 58.2716 302.023 58.2716 301.249 57.8241L262.249 35.2772C261.476 34.8304 261 34.0055 261 33.1129V-11.9879C261 -12.8805 261.476 -13.7054 262.249 -14.1522L301.249 -36.6991Z" fill="white" stroke="#EDE9FE"/>
<path d="M384.249 -36.6991C385.023 -37.1466 385.977 -37.1466 386.751 -36.6991L425.751 -14.1522C426.524 -13.7054 427 -12.8805 427 -11.9879V33.1129C427 34.0055 426.524 34.8304 425.751 35.2772L386.751 57.8241C385.977 58.2716 385.023 58.2716 384.249 57.8241L345.249 35.2772C344.476 34.8304 344 34.0055 344 33.1129V-11.9879C344 -12.8805 344.476 -13.7054 345.249 -14.1522L384.249 -36.6991Z" fill="white" stroke="#EDE9FE"/>
<path d="M467.249 -36.6991C468.023 -37.1466 468.977 -37.1466 469.751 -36.6991L508.751 -14.1522C509.524 -13.7054 510 -12.8805 510 -11.9879V33.1129C510 34.0055 509.524 34.8304 508.751 35.2772L469.751 57.8241C468.977 58.2716 468.023 58.2716 467.249 57.8241L428.249 35.2772C427.476 34.8304 427 34.0055 427 33.1129V-11.9879C427 -12.8805 427.476 -13.7054 428.249 -14.1522L467.249 -36.6991Z" fill="white" stroke="#EDE9FE"/>
<path d="M550.249 -36.6991C551.023 -37.1466 551.977 -37.1466 552.751 -36.6991L591.751 -14.1522C592.524 -13.7054 593 -12.8805 593 -11.9879V33.1129C593 34.0055 592.524 34.8304 591.751 35.2772L552.751 57.8241C551.977 58.2716 551.023 58.2716 550.249 57.8241L511.249 35.2772C510.476 34.8304 510 34.0055 510 33.1129V-11.9879C510 -12.8805 510.476 -13.7054 511.249 -14.1522L550.249 -36.6991Z" fill="white" stroke="#EDE9FE"/>
<path d="M633.249 -36.6991C634.023 -37.1466 634.977 -37.1466 635.751 -36.6991L674.751 -14.1522C675.524 -13.7054 676 -12.8805 676 -11.9879V33.1129C676 34.0055 675.524 34.8304 674.751 35.2772L635.751 57.8241C634.977 58.2716 634.023 58.2716 633.249 57.8241L594.249 35.2772C593.476 34.8304 593 34.0055 593 33.1129V-11.9879C593 -12.8805 593.476 -13.7054 594.249 -14.1522L633.249 -36.6991Z" fill="white" stroke="#EDE9FE"/>
<path d="M716.249 -36.6991C717.023 -37.1466 717.977 -37.1466 718.751 -36.6991L757.751 -14.1522C758.524 -13.7054 759 -12.8805 759 -11.9879V33.1129C759 34.0055 758.524 34.8304 757.751 35.2772L718.751 57.8241C717.977 58.2716 717.023 58.2716 716.249 57.8241L677.249 35.2772C676.476 34.8304 676 34.0055 676 33.1129V-11.9879C676 -12.8805 676.476 -13.7054 677.249 -14.1522L716.249 -36.6991Z" fill="white" stroke="#EDE9FE"/>
<path d="M799.249 -36.6991C800.023 -37.1466 800.977 -37.1466 801.751 -36.6991L840.751 -14.1522C841.524 -13.7054 842 -12.8805 842 -11.9879V33.1129C842 34.0055 841.524 34.8304 840.751 35.2772L801.751 57.8241C800.977 58.2716 800.023 58.2716 799.249 57.8241L760.249 35.2772C759.476 34.8304 759 34.0055 759 33.1129V-11.9879C759 -12.8805 759.476 -13.7054 760.249 -14.1522L799.249 -36.6991Z" fill="#F5F3FF" stroke="#EDE9FE"/>
<path d="M882.249 -36.6991C883.023 -37.1466 883.977 -37.1466 884.751 -36.6991L923.751 -14.1522C924.524 -13.7054 925 -12.8805 925 -11.9879V33.1129C925 34.0055 924.524 34.8304 923.751 35.2772L884.751 57.8241C883.977 58.2716 883.023 58.2716 882.249 57.8241L843.249 35.2772C842.476 34.8304 842 34.0055 842 33.1129V-11.9879C842 -12.8805 842.476 -13.7054 843.249 -14.1522L882.249 -36.6991Z" fill="white" stroke="#EDE9FE"/>
<path d="M965.249 -36.6991C966.023 -37.1466 966.977 -37.1466 967.751 -36.6991L1006.75 -14.1522C1007.52 -13.7054 1008 -12.8805 1008 -11.9879V33.1129C1008 34.0055 1007.52 34.8304 1006.75 35.2772L967.751 57.8241C966.977 58.2716 966.023 58.2716 965.249 57.8241L926.249 35.2772C925.476 34.8304 925 34.0055 925 33.1129V-11.9879C925 -12.8805 925.476 -13.7054 926.249 -14.1522L965.249 -36.6991Z" fill="white" stroke="#EDE9FE"/>
<path d="M1048.25 -36.6991C1049.02 -37.1466 1049.98 -37.1466 1050.75 -36.6991L1089.75 -14.1522C1090.52 -13.7054 1091 -12.8805 1091 -11.9879V33.1129C1091 34.0055 1090.52 34.8304 1089.75 35.2772L1050.75 57.8241C1049.98 58.2716 1049.02 58.2716 1048.25 57.8241L1009.25 35.2772C1008.48 34.8304 1008 34.0055 1008 33.1129V-11.9879C1008 -12.8805 1008.48 -13.7054 1009.25 -14.1522L1048.25 -36.6991Z" fill="white" stroke="#EDE9FE"/>
<path d="M1131.25 -36.6991C1132.02 -37.1466 1132.98 -37.1466 1133.75 -36.6991L1172.75 -14.1522C1173.52 -13.7054 1174 -12.8805 1174 -11.9879V33.1129C1174 34.0055 1173.52 34.8304 1172.75 35.2772L1133.75 57.8241C1132.98 58.2716 1132.02 58.2716 1131.25 57.8241L1092.25 35.2772C1091.48 34.8304 1091 34.0055 1091 33.1129V-11.9879C1091 -12.8805 1091.48 -13.7054 1092.25 -14.1522L1131.25 -36.6991Z" fill="white" stroke="#EDE9FE"/>
<path d="M1214.25 -36.6991C1215.02 -37.1466 1215.98 -37.1466 1216.75 -36.6991L1255.75 -14.1522C1256.52 -13.7054 1257 -12.8805 1257 -11.9879V33.1129C1257 34.0055 1256.52 34.8304 1255.75 35.2772L1216.75 57.8241C1215.98 58.2716 1215.02 58.2716 1214.25 57.8241L1175.25 35.2772C1174.48 34.8304 1174 34.0055 1174 33.1129V-11.9879C1174 -12.8805 1174.48 -13.7054 1175.25 -14.1522L1214.25 -36.6991Z" fill="white" stroke="#EDE9FE"/>
<path d="M1297.25 -36.6991C1298.02 -37.1466 1298.98 -37.1466 1299.75 -36.6991L1338.75 -14.1522C1339.52 -13.7054 1340 -12.8805 1340 -11.9879V33.1129C1340 34.0055 1339.52 34.8304 1338.75 35.2772L1299.75 57.8241C1298.98 58.2716 1298.02 58.2716 1297.25 57.8241L1258.25 35.2772C1257.48 34.8304 1257 34.0055 1257 33.1129V-11.9879C1257 -12.8805 1257.48 -13.7054 1258.25 -14.1522L1297.25 -36.6991Z" fill="white" stroke="#EDE9FE"/>
<path d="M1380.25 -36.6991C1381.02 -37.1466 1381.98 -37.1466 1382.75 -36.6991L1421.75 -14.1522C1422.52 -13.7054 1423 -12.8805 1423 -11.9879V33.1129C1423 34.0055 1422.52 34.8304 1421.75 35.2772L1382.75 57.8241C1381.98 58.2716 1381.02 58.2716 1380.25 57.8241L1341.25 35.2772C1340.48 34.8304 1340 34.0055 1340 33.1129V-11.9879C1340 -12.8805 1340.48 -13.7054 1341.25 -14.1522L1380.25 -36.6991Z" fill="white" stroke="#EDE9FE"/>
<path d="M1463.25 -36.6991C1464.02 -37.1466 1464.98 -37.1466 1465.75 -36.6991L1504.75 -14.1522C1505.52 -13.7054 1506 -12.8805 1506 -11.9879V33.1129C1506 34.0055 1505.52 34.8304 1504.75 35.2772L1465.75 57.8241C1464.98 58.2716 1464.02 58.2716 1463.25 57.8241L1424.25 35.2772C1423.48 34.8304 1423 34.0055 1423 33.1129V-11.9879C1423 -12.8805 1423.48 -13.7054 1424.25 -14.1522L1463.25 -36.6991Z" fill="white" stroke="#EDE9FE"/>
<path d="M10.7487 34.4259C11.5229 33.9784 12.4771 33.9784 13.2513 34.4259L52.2513 56.9728C53.024 57.4196 53.5 58.2445 53.5 59.1371V104.238C53.5 105.131 53.024 105.955 52.2513 106.402L13.2513 128.949C12.4771 129.397 11.5229 129.397 10.7487 128.949L-28.2513 106.402C-29.0241 105.955 -29.5 105.131 -29.5 104.238V59.1371C-29.5 58.2445 -29.0241 57.4196 -28.2513 56.9728L10.7487 34.4259Z" fill="white" stroke="#EDE9FE"/>
<path d="M93.7487 34.4259C94.5229 33.9784 95.4771 33.9784 96.2513 34.4259L135.251 56.9728C136.024 57.4196 136.5 58.2445 136.5 59.1371V104.238C136.5 105.131 136.024 105.955 135.251 106.402L96.2513 128.949C95.4771 129.397 94.5229 129.397 93.7487 128.949L54.7487 106.402C53.9759 105.955 53.5 105.131 53.5 104.238V59.1371C53.5 58.2445 53.9759 57.4196 54.7487 56.9728L93.7487 34.4259Z" fill="white" stroke="#EDE9FE"/>
<path d="M176.749 34.4259C177.523 33.9784 178.477 33.9784 179.251 34.4259L218.251 56.9728C219.024 57.4196 219.5 58.2445 219.5 59.1371V104.238C219.5 105.131 219.024 105.955 218.251 106.402L179.251 128.949C178.477 129.397 177.523 129.397 176.749 128.949L137.749 106.402C136.976 105.955 136.5 105.131 136.5 104.238V59.1371C136.5 58.2445 136.976 57.4196 137.749 56.9728L176.749 34.4259Z" fill="white" stroke="#EDE9FE"/>
<path d="M259.749 34.4259C260.523 33.9784 261.477 33.9784 262.251 34.4259L301.251 56.9728C302.024 57.4196 302.5 58.2445 302.5 59.1371V104.238C302.5 105.131 302.024 105.955 301.251 106.402L262.251 128.949C261.477 129.397 260.523 129.397 259.749 128.949L220.749 106.402C219.976 105.955 219.5 105.131 219.5 104.238V59.1371C219.5 58.2445 219.976 57.4196 220.749 56.9728L259.749 34.4259Z" fill="white" stroke="#EDE9FE"/>
<path d="M342.749 34.4259C343.523 33.9784 344.477 33.9784 345.251 34.4259L384.251 56.9728C385.024 57.4196 385.5 58.2445 385.5 59.1371V104.238C385.5 105.131 385.024 105.955 384.251 106.402L345.251 128.949C344.477 129.397 343.523 129.397 342.749 128.949L303.749 106.402C302.976 105.955 302.5 105.131 302.5 104.238V59.1371C302.5 58.2445 302.976 57.4196 303.749 56.9728L342.749 34.4259Z" fill="white" stroke="#EDE9FE"/>
<path d="M425.749 34.4259C426.523 33.9784 427.477 33.9784 428.251 34.4259L467.251 56.9728C468.024 57.4196 468.5 58.2445 468.5 59.1371V104.238C468.5 105.131 468.024 105.955 467.251 106.402L428.251 128.949C427.477 129.397 426.523 129.397 425.749 128.949L386.749 106.402C385.976 105.955 385.5 105.131 385.5 104.238V59.1371C385.5 58.2445 385.976 57.4196 386.749 56.9728L425.749 34.4259Z" fill="white" stroke="#EDE9FE"/>
<path d="M508.749 34.4259C509.523 33.9784 510.477 33.9784 511.251 34.4259L550.251 56.9728C551.024 57.4196 551.5 58.2445 551.5 59.1371V104.238C551.5 105.131 551.024 105.955 550.251 106.402L511.251 128.949C510.477 129.397 509.523 129.397 508.749 128.949L469.749 106.402C468.976 105.955 468.5 105.131 468.5 104.238V59.1371C468.5 58.2445 468.976 57.4196 469.749 56.9728L508.749 34.4259Z" fill="white" stroke="#EDE9FE"/>
<path d="M591.749 34.4259C592.523 33.9784 593.477 33.9784 594.251 34.4259L633.251 56.9728C634.024 57.4196 634.5 58.2445 634.5 59.1371V104.238C634.5 105.131 634.024 105.955 633.251 106.402L594.251 128.949C593.477 129.397 592.523 129.397 591.749 128.949L552.749 106.402C551.976 105.955 551.5 105.131 551.5 104.238V59.1371C551.5 58.2445 551.976 57.4196 552.749 56.9728L591.749 34.4259Z" fill="white" stroke="#EDE9FE"/>
<path d="M674.749 34.4259C675.523 33.9784 676.477 33.9784 677.251 34.4259L716.251 56.9728C717.024 57.4196 717.5 58.2445 717.5 59.1371V104.238C717.5 105.131 717.024 105.955 716.251 106.402L677.251 128.949C676.477 129.397 675.523 129.397 674.749 128.949L635.749 106.402C634.976 105.955 634.5 105.131 634.5 104.238V59.1371C634.5 58.2445 634.976 57.4196 635.749 56.9728L674.749 34.4259Z" fill="white" stroke="#EDE9FE"/>
<path d="M757.749 34.4259C758.523 33.9784 759.477 33.9784 760.251 34.4259L799.251 56.9728C800.024 57.4196 800.5 58.2445 800.5 59.1371V104.238C800.5 105.131 800.024 105.955 799.251 106.402L760.251 128.949C759.477 129.397 758.523 129.397 757.749 128.949L718.749 106.402C717.976 105.955 717.5 105.131 717.5 104.238V59.1371C717.5 58.2445 717.976 57.4196 718.749 56.9728L757.749 34.4259Z" fill="white" stroke="#EDE9FE"/>
<path d="M840.749 34.4259C841.523 33.9784 842.477 33.9784 843.251 34.4259L882.251 56.9728C883.024 57.4196 883.5 58.2445 883.5 59.1371V104.238C883.5 105.131 883.024 105.955 882.251 106.402L843.251 128.949C842.477 129.397 841.523 129.397 840.749 128.949L801.749 106.402C800.976 105.955 800.5 105.131 800.5 104.238V59.1371C800.5 58.2445 800.976 57.4196 801.749 56.9728L840.749 34.4259Z" fill="white" stroke="#EDE9FE"/>
<path d="M923.749 34.4259C924.523 33.9784 925.477 33.9784 926.251 34.4259L965.251 56.9728C966.024 57.4196 966.5 58.2445 966.5 59.1371V104.238C966.5 105.131 966.024 105.955 965.251 106.402L926.251 128.949C925.477 129.397 924.523 129.397 923.749 128.949L884.749 106.402C883.976 105.955 883.5 105.131 883.5 104.238V59.1371C883.5 58.2445 883.976 57.4196 884.749 56.9728L923.749 34.4259Z" fill="white" stroke="#EDE9FE"/>
<path d="M1006.75 34.4259C1007.52 33.9784 1008.48 33.9784 1009.25 34.4259L1048.25 56.9728C1049.02 57.4196 1049.5 58.2445 1049.5 59.1371V104.238C1049.5 105.131 1049.02 105.955 1048.25 106.402L1009.25 128.949C1008.48 129.397 1007.52 129.397 1006.75 128.949L967.749 106.402C966.976 105.955 966.5 105.131 966.5 104.238V59.1371C966.5 58.2445 966.976 57.4196 967.749 56.9728L1006.75 34.4259Z" fill="white" stroke="#EDE9FE"/>
<path d="M1089.75 34.4259C1090.52 33.9784 1091.48 33.9784 1092.25 34.4259L1131.25 56.9728C1132.02 57.4196 1132.5 58.2445 1132.5 59.1371V104.238C1132.5 105.131 1132.02 105.955 1131.25 106.402L1092.25 128.949C1091.48 129.397 1090.52 129.397 1089.75 128.949L1050.75 106.402C1049.98 105.955 1049.5 105.131 1049.5 104.238V59.1371C1049.5 58.2445 1049.98 57.4196 1050.75 56.9728L1089.75 34.4259Z" fill="white" stroke="#EDE9FE"/>
<path d="M1172.75 34.4259C1173.52 33.9784 1174.48 33.9784 1175.25 34.4259L1214.25 56.9728C1215.02 57.4196 1215.5 58.2445 1215.5 59.1371V104.238C1215.5 105.131 1215.02 105.955 1214.25 106.402L1175.25 128.949C1174.48 129.397 1173.52 129.397 1172.75 128.949L1133.75 106.402C1132.98 105.955 1132.5 105.131 1132.5 104.238V59.1371C1132.5 58.2445 1132.98 57.4196 1133.75 56.9728L1172.75 34.4259Z" fill="white" stroke="#EDE9FE"/>
<path d="M1255.75 34.4259C1256.52 33.9784 1257.48 33.9784 1258.25 34.4259L1297.25 56.9728C1298.02 57.4196 1298.5 58.2445 1298.5 59.1371V104.238C1298.5 105.131 1298.02 105.955 1297.25 106.402L1258.25 128.949C1257.48 129.397 1256.52 129.397 1255.75 128.949L1216.75 106.402C1215.98 105.955 1215.5 105.131 1215.5 104.238V59.1371C1215.5 58.2445 1215.98 57.4196 1216.75 56.9728L1255.75 34.4259Z" fill="white" stroke="#EDE9FE"/>
<path d="M1338.75 34.4259C1339.52 33.9784 1340.48 33.9784 1341.25 34.4259L1380.25 56.9728C1381.02 57.4196 1381.5 58.2445 1381.5 59.1371V104.238C1381.5 105.131 1381.02 105.955 1380.25 106.402L1341.25 128.949C1340.48 129.397 1339.52 129.397 1338.75 128.949L1299.75 106.402C1298.98 105.955 1298.5 105.131 1298.5 104.238V59.1371C1298.5 58.2445 1298.98 57.4196 1299.75 56.9728L1338.75 34.4259Z" fill="white" stroke="#EDE9FE"/>
<path d="M1421.75 34.4259C1422.52 33.9784 1423.48 33.9784 1424.25 34.4259L1463.25 56.9728C1464.02 57.4196 1464.5 58.2445 1464.5 59.1371V104.238C1464.5 105.131 1464.02 105.955 1463.25 106.402L1424.25 128.949C1423.48 129.397 1422.52 129.397 1421.75 128.949L1382.75 106.402C1381.98 105.955 1381.5 105.131 1381.5 104.238V59.1371C1381.5 58.2445 1381.98 57.4196 1382.75 56.9728L1421.75 34.4259Z" fill="white" stroke="#EDE9FE"/>
<path d="M52.2487 105.551C53.0229 105.103 53.9771 105.103 54.7513 105.551L93.7513 128.098C94.524 128.545 95 129.369 95 130.262V175.363C95 176.256 94.524 177.08 93.7513 177.527L54.7513 200.074C53.9771 200.522 53.0229 200.522 52.2487 200.074L13.2487 177.527C12.4759 177.08 12 176.256 12 175.363V130.262C12 129.369 12.4759 128.545 13.2487 128.098L52.2487 105.551Z" fill="white" stroke="#EDE9FE"/>
<path d="M135.249 105.551C136.023 105.103 136.977 105.103 137.751 105.551L176.751 128.098C177.524 128.545 178 129.369 178 130.262V175.363C178 176.256 177.524 177.08 176.751 177.527L137.751 200.074C136.977 200.522 136.023 200.522 135.249 200.074L96.2487 177.527C95.4759 177.08 95 176.256 95 175.363V130.262C95 129.369 95.4759 128.545 96.2487 128.098L135.249 105.551Z" fill="white" stroke="#EDE9FE"/>
<path d="M218.249 105.551C219.023 105.103 219.977 105.103 220.751 105.551L259.751 128.098C260.524 128.545 261 129.369 261 130.262V175.363C261 176.256 260.524 177.08 259.751 177.527L220.751 200.074C219.977 200.522 219.023 200.522 218.249 200.074L179.249 177.527C178.476 177.08 178 176.256 178 175.363V130.262C178 129.369 178.476 128.545 179.249 128.098L218.249 105.551Z" fill="white" stroke="#EDE9FE"/>
<path d="M301.249 105.551C302.023 105.103 302.977 105.103 303.751 105.551L342.751 128.098C343.524 128.545 344 129.369 344 130.262V175.363C344 176.256 343.524 177.08 342.751 177.527L303.751 200.074C302.977 200.522 302.023 200.522 301.249 200.074L262.249 177.527C261.476 177.08 261 176.256 261 175.363V130.262C261 129.369 261.476 128.545 262.249 128.098L301.249 105.551Z" fill="white" stroke="#EDE9FE"/>
<path d="M384.249 105.551C385.023 105.103 385.977 105.103 386.751 105.551L425.751 128.098C426.524 128.545 427 129.369 427 130.262V175.363C427 176.256 426.524 177.08 425.751 177.527L386.751 200.074C385.977 200.522 385.023 200.522 384.249 200.074L345.249 177.527C344.476 177.08 344 176.256 344 175.363V130.262C344 129.369 344.476 128.545 345.249 128.098L384.249 105.551Z" fill="#F5F3FF" stroke="#EDE9FE"/>
<path d="M467.249 105.551C468.023 105.103 468.977 105.103 469.751 105.551L508.751 128.098C509.524 128.545 510 129.369 510 130.262V175.363C510 176.256 509.524 177.08 508.751 177.527L469.751 200.074C468.977 200.522 468.023 200.522 467.249 200.074L428.249 177.527C427.476 177.08 427 176.256 427 175.363V130.262C427 129.369 427.476 128.545 428.249 128.098L467.249 105.551Z" fill="white" stroke="#EDE9FE"/>
<path d="M550.249 105.551C551.023 105.103 551.977 105.103 552.751 105.551L591.751 128.098C592.524 128.545 593 129.369 593 130.262V175.363C593 176.256 592.524 177.08 591.751 177.527L552.751 200.074C551.977 200.522 551.023 200.522 550.249 200.074L511.249 177.527C510.476 177.08 510 176.256 510 175.363V130.262C510 129.369 510.476 128.545 511.249 128.098L550.249 105.551Z" fill="white" stroke="#EDE9FE"/>
<path d="M633.249 105.551C634.023 105.103 634.977 105.103 635.751 105.551L674.751 128.098C675.524 128.545 676 129.369 676 130.262V175.363C676 176.256 675.524 177.08 674.751 177.527L635.751 200.074C634.977 200.522 634.023 200.522 633.249 200.074L594.249 177.527C593.476 177.08 593 176.256 593 175.363V130.262C593 129.369 593.476 128.545 594.249 128.098L633.249 105.551Z" fill="white" stroke="#EDE9FE"/>
<path d="M716.249 105.551C717.023 105.103 717.977 105.103 718.751 105.551L757.751 128.098C758.524 128.545 759 129.369 759 130.262V175.363C759 176.256 758.524 177.08 757.751 177.527L718.751 200.074C717.977 200.522 717.023 200.522 716.249 200.074L677.249 177.527C676.476 177.08 676 176.256 676 175.363V130.262C676 129.369 676.476 128.545 677.249 128.098L716.249 105.551Z" fill="white" stroke="#EDE9FE"/>
<path d="M799.249 105.551C800.023 105.103 800.977 105.103 801.751 105.551L840.751 128.098C841.524 128.545 842 129.369 842 130.262V175.363C842 176.256 841.524 177.08 840.751 177.527L801.751 200.074C800.977 200.522 800.023 200.522 799.249 200.074L760.249 177.527C759.476 177.08 759 176.256 759 175.363V130.262C759 129.369 759.476 128.545 760.249 128.098L799.249 105.551Z" fill="white" stroke="#EDE9FE"/>
<path d="M882.249 105.551C883.023 105.103 883.977 105.103 884.751 105.551L923.751 128.098C924.524 128.545 925 129.369 925 130.262V175.363C925 176.256 924.524 177.08 923.751 177.527L884.751 200.074C883.977 200.522 883.023 200.522 882.249 200.074L843.249 177.527C842.476 177.08 842 176.256 842 175.363V130.262C842 129.369 842.476 128.545 843.249 128.098L882.249 105.551Z" fill="white" stroke="#EDE9FE"/>
<path d="M965.249 105.551C966.023 105.103 966.977 105.103 967.751 105.551L1006.75 128.098C1007.52 128.545 1008 129.369 1008 130.262V175.363C1008 176.256 1007.52 177.08 1006.75 177.527L967.751 200.074C966.977 200.522 966.023 200.522 965.249 200.074L926.249 177.527C925.476 177.08 925 176.256 925 175.363V130.262C925 129.369 925.476 128.545 926.249 128.098L965.249 105.551Z" fill="white" stroke="#EDE9FE"/>
<path d="M1048.25 105.551C1049.02 105.103 1049.98 105.103 1050.75 105.551L1089.75 128.098C1090.52 128.545 1091 129.369 1091 130.262V175.363C1091 176.256 1090.52 177.08 1089.75 177.527L1050.75 200.074C1049.98 200.522 1049.02 200.522 1048.25 200.074L1009.25 177.527C1008.48 177.08 1008 176.256 1008 175.363V130.262C1008 129.369 1008.48 128.545 1009.25 128.098L1048.25 105.551Z" fill="white" stroke="#EDE9FE"/>
<path d="M1131.25 105.551C1132.02 105.103 1132.98 105.103 1133.75 105.551L1172.75 128.098C1173.52 128.545 1174 129.369 1174 130.262V175.363C1174 176.256 1173.52 177.08 1172.75 177.527L1133.75 200.074C1132.98 200.522 1132.02 200.522 1131.25 200.074L1092.25 177.527C1091.48 177.08 1091 176.256 1091 175.363V130.262C1091 129.369 1091.48 128.545 1092.25 128.098L1131.25 105.551Z" fill="#F5F3FF" stroke="#EDE9FE"/>
<path d="M1214.25 105.551C1215.02 105.103 1215.98 105.103 1216.75 105.551L1255.75 128.098C1256.52 128.545 1257 129.369 1257 130.262V175.363C1257 176.256 1256.52 177.08 1255.75 177.527L1216.75 200.074C1215.98 200.522 1215.02 200.522 1214.25 200.074L1175.25 177.527C1174.48 177.08 1174 176.256 1174 175.363V130.262C1174 129.369 1174.48 128.545 1175.25 128.098L1214.25 105.551Z" fill="white" stroke="#EDE9FE"/>
<path d="M1297.25 105.551C1298.02 105.103 1298.98 105.103 1299.75 105.551L1338.75 128.098C1339.52 128.545 1340 129.369 1340 130.262V175.363C1340 176.256 1339.52 177.08 1338.75 177.527L1299.75 200.074C1298.98 200.522 1298.02 200.522 1297.25 200.074L1258.25 177.527C1257.48 177.08 1257 176.256 1257 175.363V130.262C1257 129.369 1257.48 128.545 1258.25 128.098L1297.25 105.551Z" fill="white" stroke="#EDE9FE"/>
<path d="M1380.25 105.551C1381.02 105.103 1381.98 105.103 1382.75 105.551L1421.75 128.098C1422.52 128.545 1423 129.369 1423 130.262V175.363C1423 176.256 1422.52 177.08 1421.75 177.527L1382.75 200.074C1381.98 200.522 1381.02 200.522 1380.25 200.074L1341.25 177.527C1340.48 177.08 1340 176.256 1340 175.363V130.262C1340 129.369 1340.48 128.545 1341.25 128.098L1380.25 105.551Z" fill="white" stroke="#EDE9FE"/>
<path d="M1463.25 105.551C1464.02 105.103 1464.98 105.103 1465.75 105.551L1504.75 128.098C1505.52 128.545 1506 129.369 1506 130.262V175.363C1506 176.256 1505.52 177.08 1504.75 177.527L1465.75 200.074C1464.98 200.522 1464.02 200.522 1463.25 200.074L1424.25 177.527C1423.48 177.08 1423 176.256 1423 175.363V130.262C1423 129.369 1423.48 128.545 1424.25 128.098L1463.25 105.551Z" fill="white" stroke="#EDE9FE"/>
<path d="M10.7487 176.676C11.5229 176.228 12.4771 176.228 13.2513 176.676L52.2513 199.223C53.024 199.67 53.5 200.494 53.5 201.387V246.488C53.5 247.381 53.024 248.205 52.2513 248.652L13.2513 271.199C12.4771 271.647 11.5229 271.647 10.7487 271.199L-28.2513 248.652C-29.0241 248.205 -29.5 247.381 -29.5 246.488V201.387C-29.5 200.494 -29.0241 199.67 -28.2513 199.223L10.7487 176.676Z" fill="white" stroke="#EDE9FE"/>
<path d="M93.7487 176.676C94.5229 176.228 95.4771 176.228 96.2513 176.676L135.251 199.223C136.024 199.67 136.5 200.494 136.5 201.387V246.488C136.5 247.381 136.024 248.205 135.251 248.652L96.2513 271.199C95.4771 271.647 94.5229 271.647 93.7487 271.199L54.7487 248.652C53.9759 248.205 53.5 247.381 53.5 246.488V201.387C53.5 200.494 53.9759 199.67 54.7487 199.223L93.7487 176.676Z" fill="white" stroke="#EDE9FE"/>
<path d="M176.749 176.676C177.523 176.228 178.477 176.228 179.251 176.676L218.251 199.223C219.024 199.67 219.5 200.494 219.5 201.387V246.488C219.5 247.381 219.024 248.205 218.251 248.652L179.251 271.199C178.477 271.647 177.523 271.647 176.749 271.199L137.749 248.652C136.976 248.205 136.5 247.381 136.5 246.488V201.387C136.5 200.494 136.976 199.67 137.749 199.223L176.749 176.676Z" fill="white" stroke="#EDE9FE"/>
<path d="M259.749 176.676C260.523 176.228 261.477 176.228 262.251 176.676L301.251 199.223C302.024 199.67 302.5 200.494 302.5 201.387V246.488C302.5 247.381 302.024 248.205 301.251 248.652L262.251 271.199C261.477 271.647 260.523 271.647 259.749 271.199L220.749 248.652C219.976 248.205 219.5 247.381 219.5 246.488V201.387C219.5 200.494 219.976 199.67 220.749 199.223L259.749 176.676Z" fill="white" stroke="#EDE9FE"/>
<path d="M342.749 176.676C343.523 176.228 344.477 176.228 345.251 176.676L384.251 199.223C385.024 199.67 385.5 200.494 385.5 201.387V246.488C385.5 247.381 385.024 248.205 384.251 248.652L345.251 271.199C344.477 271.647 343.523 271.647 342.749 271.199L303.749 248.652C302.976 248.205 302.5 247.381 302.5 246.488V201.387C302.5 200.494 302.976 199.67 303.749 199.223L342.749 176.676Z" fill="white" stroke="#EDE9FE"/>
<path d="M425.749 176.676C426.523 176.228 427.477 176.228 428.251 176.676L467.251 199.223C468.024 199.67 468.5 200.494 468.5 201.387V246.488C468.5 247.381 468.024 248.205 467.251 248.652L428.251 271.199C427.477 271.647 426.523 271.647 425.749 271.199L386.749 248.652C385.976 248.205 385.5 247.381 385.5 246.488V201.387C385.5 200.494 385.976 199.67 386.749 199.223L425.749 176.676Z" fill="white" stroke="#EDE9FE"/>
<path d="M508.749 176.676C509.523 176.228 510.477 176.228 511.251 176.676L550.251 199.223C551.024 199.67 551.5 200.494 551.5 201.387V246.488C551.5 247.381 551.024 248.205 550.251 248.652L511.251 271.199C510.477 271.647 509.523 271.647 508.749 271.199L469.749 248.652C468.976 248.205 468.5 247.381 468.5 246.488V201.387C468.5 200.494 468.976 199.67 469.749 199.223L508.749 176.676Z" fill="white" stroke="#EDE9FE"/>
<path d="M591.749 176.676C592.523 176.228 593.477 176.228 594.251 176.676L633.251 199.223C634.024 199.67 634.5 200.494 634.5 201.387V246.488C634.5 247.381 634.024 248.205 633.251 248.652L594.251 271.199C593.477 271.647 592.523 271.647 591.749 271.199L552.749 248.652C551.976 248.205 551.5 247.381 551.5 246.488V201.387C551.5 200.494 551.976 199.67 552.749 199.223L591.749 176.676Z" fill="white" stroke="#EDE9FE"/>
<path d="M674.749 176.676C675.523 176.228 676.477 176.228 677.251 176.676L716.251 199.223C717.024 199.67 717.5 200.494 717.5 201.387V246.488C717.5 247.381 717.024 248.205 716.251 248.652L677.251 271.199C676.477 271.647 675.523 271.647 674.749 271.199L635.749 248.652C634.976 248.205 634.5 247.381 634.5 246.488V201.387C634.5 200.494 634.976 199.67 635.749 199.223L674.749 176.676Z" fill="white" stroke="#EDE9FE"/>
<path d="M757.749 176.676C758.523 176.228 759.477 176.228 760.251 176.676L799.251 199.223C800.024 199.67 800.5 200.494 800.5 201.387V246.488C800.5 247.381 800.024 248.205 799.251 248.652L760.251 271.199C759.477 271.647 758.523 271.647 757.749 271.199L718.749 248.652C717.976 248.205 717.5 247.381 717.5 246.488V201.387C717.5 200.494 717.976 199.67 718.749 199.223L757.749 176.676Z" fill="white" stroke="#EDE9FE"/>
<path d="M840.749 176.676C841.523 176.228 842.477 176.228 843.251 176.676L882.251 199.223C883.024 199.67 883.5 200.494 883.5 201.387V246.488C883.5 247.381 883.024 248.205 882.251 248.652L843.251 271.199C842.477 271.647 841.523 271.647 840.749 271.199L801.749 248.652C800.976 248.205 800.5 247.381 800.5 246.488V201.387C800.5 200.494 800.976 199.67 801.749 199.223L840.749 176.676Z" fill="white" stroke="#EDE9FE"/>
<path d="M923.749 176.676C924.523 176.228 925.477 176.228 926.251 176.676L965.251 199.223C966.024 199.67 966.5 200.494 966.5 201.387V246.488C966.5 247.381 966.024 248.205 965.251 248.652L926.251 271.199C925.477 271.647 924.523 271.647 923.749 271.199L884.749 248.652C883.976 248.205 883.5 247.381 883.5 246.488V201.387C883.5 200.494 883.976 199.67 884.749 199.223L923.749 176.676Z" fill="white" stroke="#EDE9FE"/>
<path d="M1006.75 176.676C1007.52 176.228 1008.48 176.228 1009.25 176.676L1048.25 199.223C1049.02 199.67 1049.5 200.494 1049.5 201.387V246.488C1049.5 247.381 1049.02 248.205 1048.25 248.652L1009.25 271.199C1008.48 271.647 1007.52 271.647 1006.75 271.199L967.749 248.652C966.976 248.205 966.5 247.381 966.5 246.488V201.387C966.5 200.494 966.976 199.67 967.749 199.223L1006.75 176.676Z" fill="white" stroke="#EDE9FE"/>
<path d="M1089.75 176.676C1090.52 176.228 1091.48 176.228 1092.25 176.676L1131.25 199.223C1132.02 199.67 1132.5 200.494 1132.5 201.387V246.488C1132.5 247.381 1132.02 248.205 1131.25 248.652L1092.25 271.199C1091.48 271.647 1090.52 271.647 1089.75 271.199L1050.75 248.652C1049.98 248.205 1049.5 247.381 1049.5 246.488V201.387C1049.5 200.494 1049.98 199.67 1050.75 199.223L1089.75 176.676Z" fill="white" stroke="#EDE9FE"/>
<path d="M1172.75 176.676C1173.52 176.228 1174.48 176.228 1175.25 176.676L1214.25 199.223C1215.02 199.67 1215.5 200.494 1215.5 201.387V246.488C1215.5 247.381 1215.02 248.205 1214.25 248.652L1175.25 271.199C1174.48 271.647 1173.52 271.647 1172.75 271.199L1133.75 248.652C1132.98 248.205 1132.5 247.381 1132.5 246.488V201.387C1132.5 200.494 1132.98 199.67 1133.75 199.223L1172.75 176.676Z" fill="white" stroke="#EDE9FE"/>
<path d="M1255.75 176.676C1256.52 176.228 1257.48 176.228 1258.25 176.676L1297.25 199.223C1298.02 199.67 1298.5 200.494 1298.5 201.387V246.488C1298.5 247.381 1298.02 248.205 1297.25 248.652L1258.25 271.199C1257.48 271.647 1256.52 271.647 1255.75 271.199L1216.75 248.652C1215.98 248.205 1215.5 247.381 1215.5 246.488V201.387C1215.5 200.494 1215.98 199.67 1216.75 199.223L1255.75 176.676Z" fill="white" stroke="#EDE9FE"/>
<path d="M1338.75 176.676C1339.52 176.228 1340.48 176.228 1341.25 176.676L1380.25 199.223C1381.02 199.67 1381.5 200.494 1381.5 201.387V246.488C1381.5 247.381 1381.02 248.205 1380.25 248.652L1341.25 271.199C1340.48 271.647 1339.52 271.647 1338.75 271.199L1299.75 248.652C1298.98 248.205 1298.5 247.381 1298.5 246.488V201.387C1298.5 200.494 1298.98 199.67 1299.75 199.223L1338.75 176.676Z" fill="white" stroke="#EDE9FE"/>
<path d="M1421.75 176.676C1422.52 176.228 1423.48 176.228 1424.25 176.676L1463.25 199.223C1464.02 199.67 1464.5 200.494 1464.5 201.387V246.488C1464.5 247.381 1464.02 248.205 1463.25 248.652L1424.25 271.199C1423.48 271.647 1422.52 271.647 1421.75 271.199L1382.75 248.652C1381.98 248.205 1381.5 247.381 1381.5 246.488V201.387C1381.5 200.494 1381.98 199.67 1382.75 199.223L1421.75 176.676Z" fill="white" stroke="#EDE9FE"/>
<path d="M52.2487 247.801C53.0229 247.353 53.9771 247.353 54.7513 247.801L93.7513 270.348C94.524 270.795 95 271.619 95 272.512V317.613C95 318.506 94.524 319.33 93.7513 319.777L54.7513 342.324C53.9771 342.772 53.0229 342.772 52.2487 342.324L13.2487 319.777C12.4759 319.33 12 318.506 12 317.613V272.512C12 271.619 12.4759 270.795 13.2487 270.348L52.2487 247.801Z" fill="white" stroke="#EDE9FE"/>
<path d="M135.249 247.801C136.023 247.353 136.977 247.353 137.751 247.801L176.751 270.348C177.524 270.795 178 271.619 178 272.512V317.613C178 318.506 177.524 319.33 176.751 319.777L137.751 342.324C136.977 342.772 136.023 342.772 135.249 342.324L96.2487 319.777C95.4759 319.33 95 318.506 95 317.613V272.512C95 271.619 95.4759 270.795 96.2487 270.348L135.249 247.801Z" fill="white" stroke="#EDE9FE"/>
<path d="M218.249 247.801C219.023 247.353 219.977 247.353 220.751 247.801L259.751 270.348C260.524 270.795 261 271.619 261 272.512V317.613C261 318.506 260.524 319.33 259.751 319.777L220.751 342.324C219.977 342.772 219.023 342.772 218.249 342.324L179.249 319.777C178.476 319.33 178 318.506 178 317.613V272.512C178 271.619 178.476 270.795 179.249 270.348L218.249 247.801Z" fill="white" stroke="#EDE9FE"/>
<path d="M301.249 247.801C302.023 247.353 302.977 247.353 303.751 247.801L342.751 270.348C343.524 270.795 344 271.619 344 272.512V317.613C344 318.506 343.524 319.33 342.751 319.777L303.751 342.324C302.977 342.772 302.023 342.772 301.249 342.324L262.249 319.777C261.476 319.33 261 318.506 261 317.613V272.512C261 271.619 261.476 270.795 262.249 270.348L301.249 247.801Z" fill="white" stroke="#EDE9FE"/>
<path d="M384.249 247.801C385.023 247.353 385.977 247.353 386.751 247.801L425.751 270.348C426.524 270.795 427 271.619 427 272.512V317.613C427 318.506 426.524 319.33 425.751 319.777L386.751 342.324C385.977 342.772 385.023 342.772 384.249 342.324L345.249 319.777C344.476 319.33 344 318.506 344 317.613V272.512C344 271.619 344.476 270.795 345.249 270.348L384.249 247.801Z" fill="white" stroke="#EDE9FE"/>
<path d="M467.249 247.801C468.023 247.353 468.977 247.353 469.751 247.801L508.751 270.348C509.524 270.795 510 271.619 510 272.512V317.613C510 318.506 509.524 319.33 508.751 319.777L469.751 342.324C468.977 342.772 468.023 342.772 467.249 342.324L428.249 319.777C427.476 319.33 427 318.506 427 317.613V272.512C427 271.619 427.476 270.795 428.249 270.348L467.249 247.801Z" fill="white" stroke="#EDE9FE"/>
<path d="M550.249 247.801C551.023 247.353 551.977 247.353 552.751 247.801L591.751 270.348C592.524 270.795 593 271.619 593 272.512V317.613C593 318.506 592.524 319.33 591.751 319.777L552.751 342.324C551.977 342.772 551.023 342.772 550.249 342.324L511.249 319.777C510.476 319.33 510 318.506 510 317.613V272.512C510 271.619 510.476 270.795 511.249 270.348L550.249 247.801Z" fill="#F5F3FF" stroke="#EDE9FE"/>
<path d="M633.249 247.801C634.023 247.353 634.977 247.353 635.751 247.801L674.751 270.348C675.524 270.795 676 271.619 676 272.512V317.613C676 318.506 675.524 319.33 674.751 319.777L635.751 342.324C634.977 342.772 634.023 342.772 633.249 342.324L594.249 319.777C593.476 319.33 593 318.506 593 317.613V272.512C593 271.619 593.476 270.795 594.249 270.348L633.249 247.801Z" fill="white" stroke="#EDE9FE"/>
<path d="M716.249 247.801C717.023 247.353 717.977 247.353 718.751 247.801L757.751 270.348C758.524 270.795 759 271.619 759 272.512V317.613C759 318.506 758.524 319.33 757.751 319.777L718.751 342.324C717.977 342.772 717.023 342.772 716.249 342.324L677.249 319.777C676.476 319.33 676 318.506 676 317.613V272.512C676 271.619 676.476 270.795 677.249 270.348L716.249 247.801Z" fill="white" stroke="#EDE9FE"/>
<path d="M799.249 247.801C800.023 247.353 800.977 247.353 801.751 247.801L840.751 270.348C841.524 270.795 842 271.619 842 272.512V317.613C842 318.506 841.524 319.33 840.751 319.777L801.751 342.324C800.977 342.772 800.023 342.772 799.249 342.324L760.249 319.777C759.476 319.33 759 318.506 759 317.613V272.512C759 271.619 759.476 270.795 760.249 270.348L799.249 247.801Z" fill="white" stroke="#EDE9FE"/>
<path d="M882.249 247.801C883.023 247.353 883.977 247.353 884.751 247.801L923.751 270.348C924.524 270.795 925 271.619 925 272.512V317.613C925 318.506 924.524 319.33 923.751 319.777L884.751 342.324C883.977 342.772 883.023 342.772 882.249 342.324L843.249 319.777C842.476 319.33 842 318.506 842 317.613V272.512C842 271.619 842.476 270.795 843.249 270.348L882.249 247.801Z" fill="white" stroke="#EDE9FE"/>
<path d="M965.249 247.801C966.023 247.353 966.977 247.353 967.751 247.801L1006.75 270.348C1007.52 270.795 1008 271.619 1008 272.512V317.613C1008 318.506 1007.52 319.33 1006.75 319.777L967.751 342.324C966.977 342.772 966.023 342.772 965.249 342.324L926.249 319.777C925.476 319.33 925 318.506 925 317.613V272.512C925 271.619 925.476 270.795 926.249 270.348L965.249 247.801Z" fill="white" stroke="#EDE9FE"/>
<path d="M1048.25 247.801C1049.02 247.353 1049.98 247.353 1050.75 247.801L1089.75 270.348C1090.52 270.795 1091 271.619 1091 272.512V317.613C1091 318.506 1090.52 319.33 1089.75 319.777L1050.75 342.324C1049.98 342.772 1049.02 342.772 1048.25 342.324L1009.25 319.777C1008.48 319.33 1008 318.506 1008 317.613V272.512C1008 271.619 1008.48 270.795 1009.25 270.348L1048.25 247.801Z" fill="white" stroke="#EDE9FE"/>
<path d="M1131.25 247.801C1132.02 247.353 1132.98 247.353 1133.75 247.801L1172.75 270.348C1173.52 270.795 1174 271.619 1174 272.512V317.613C1174 318.506 1173.52 319.33 1172.75 319.777L1133.75 342.324C1132.98 342.772 1132.02 342.772 1131.25 342.324L1092.25 319.777C1091.48 319.33 1091 318.506 1091 317.613V272.512C1091 271.619 1091.48 270.795 1092.25 270.348L1131.25 247.801Z" fill="white" stroke="#EDE9FE"/>
<path d="M1214.25 247.801C1215.02 247.353 1215.98 247.353 1216.75 247.801L1255.75 270.348C1256.52 270.795 1257 271.619 1257 272.512V317.613C1257 318.506 1256.52 319.33 1255.75 319.777L1216.75 342.324C1215.98 342.772 1215.02 342.772 1214.25 342.324L1175.25 319.777C1174.48 319.33 1174 318.506 1174 317.613V272.512C1174 271.619 1174.48 270.795 1175.25 270.348L1214.25 247.801Z" fill="white" stroke="#EDE9FE"/>
<path d="M1297.25 247.801C1298.02 247.353 1298.98 247.353 1299.75 247.801L1338.75 270.348C1339.52 270.795 1340 271.619 1340 272.512V317.613C1340 318.506 1339.52 319.33 1338.75 319.777L1299.75 342.324C1298.98 342.772 1298.02 342.772 1297.25 342.324L1258.25 319.777C1257.48 319.33 1257 318.506 1257 317.613V272.512C1257 271.619 1257.48 270.795 1258.25 270.348L1297.25 247.801Z" fill="white" stroke="#EDE9FE"/>
<path d="M1380.25 247.801C1381.02 247.353 1381.98 247.353 1382.75 247.801L1421.75 270.348C1422.52 270.795 1423 271.619 1423 272.512V317.613C1423 318.506 1422.52 319.33 1421.75 319.777L1382.75 342.324C1381.98 342.772 1381.02 342.772 1380.25 342.324L1341.25 319.777C1340.48 319.33 1340 318.506 1340 317.613V272.512C1340 271.619 1340.48 270.795 1341.25 270.348L1380.25 247.801Z" fill="white" stroke="#EDE9FE"/>
<path d="M1463.25 247.801C1464.02 247.353 1464.98 247.353 1465.75 247.801L1504.75 270.348C1505.52 270.795 1506 271.619 1506 272.512V317.613C1506 318.506 1505.52 319.33 1504.75 319.777L1465.75 342.324C1464.98 342.772 1464.02 342.772 1463.25 342.324L1424.25 319.777C1423.48 319.33 1423 318.506 1423 317.613V272.512C1423 271.619 1423.48 270.795 1424.25 270.348L1463.25 247.801Z" fill="white" stroke="#EDE9FE"/>
<path d="M10.7487 318.926C11.5229 318.478 12.4771 318.478 13.2513 318.926L52.2513 341.473C53.024 341.92 53.5 342.744 53.5 343.637V388.738C53.5 389.631 53.024 390.455 52.2513 390.902L13.2513 413.449C12.4771 413.897 11.5229 413.897 10.7487 413.449L-28.2513 390.902C-29.0241 390.455 -29.5 389.631 -29.5 388.738V343.637C-29.5 342.744 -29.0241 341.92 -28.2513 341.473L10.7487 318.926Z" fill="white" stroke="#EDE9FE"/>
<path d="M93.7487 318.926C94.5229 318.478 95.4771 318.478 96.2513 318.926L135.251 341.473C136.024 341.92 136.5 342.744 136.5 343.637V388.738C136.5 389.631 136.024 390.455 135.251 390.902L96.2513 413.449C95.4771 413.897 94.5229 413.897 93.7487 413.449L54.7487 390.902C53.9759 390.455 53.5 389.631 53.5 388.738V343.637C53.5 342.744 53.9759 341.92 54.7487 341.473L93.7487 318.926Z" fill="white" stroke="#EDE9FE"/>
<path d="M176.749 318.926C177.523 318.478 178.477 318.478 179.251 318.926L218.251 341.473C219.024 341.92 219.5 342.744 219.5 343.637V388.738C219.5 389.631 219.024 390.455 218.251 390.902L179.251 413.449C178.477 413.897 177.523 413.897 176.749 413.449L137.749 390.902C136.976 390.455 136.5 389.631 136.5 388.738V343.637C136.5 342.744 136.976 341.92 137.749 341.473L176.749 318.926Z" fill="white" stroke="#EDE9FE"/>
<path d="M259.749 318.926C260.523 318.478 261.477 318.478 262.251 318.926L301.251 341.473C302.024 341.92 302.5 342.744 302.5 343.637V388.738C302.5 389.631 302.024 390.455 301.251 390.902L262.251 413.449C261.477 413.897 260.523 413.897 259.749 413.449L220.749 390.902C219.976 390.455 219.5 389.631 219.5 388.738V343.637C219.5 342.744 219.976 341.92 220.749 341.473L259.749 318.926Z" fill="white" stroke="#EDE9FE"/>
<path d="M342.749 318.926C343.523 318.478 344.477 318.478 345.251 318.926L384.251 341.473C385.024 341.92 385.5 342.744 385.5 343.637V388.738C385.5 389.631 385.024 390.455 384.251 390.902L345.251 413.449C344.477 413.897 343.523 413.897 342.749 413.449L303.749 390.902C302.976 390.455 302.5 389.631 302.5 388.738V343.637C302.5 342.744 302.976 341.92 303.749 341.473L342.749 318.926Z" fill="white" stroke="#EDE9FE"/>
<path d="M425.749 318.926C426.523 318.478 427.477 318.478 428.251 318.926L467.251 341.473C468.024 341.92 468.5 342.744 468.5 343.637V388.738C468.5 389.631 468.024 390.455 467.251 390.902L428.251 413.449C427.477 413.897 426.523 413.897 425.749 413.449L386.749 390.902C385.976 390.455 385.5 389.631 385.5 388.738V343.637C385.5 342.744 385.976 341.92 386.749 341.473L425.749 318.926Z" fill="white" stroke="#EDE9FE"/>
<path d="M508.749 318.926C509.523 318.478 510.477 318.478 511.251 318.926L550.251 341.473C551.024 341.92 551.5 342.744 551.5 343.637V388.738C551.5 389.631 551.024 390.455 550.251 390.902L511.251 413.449C510.477 413.897 509.523 413.897 508.749 413.449L469.749 390.902C468.976 390.455 468.5 389.631 468.5 388.738V343.637C468.5 342.744 468.976 341.92 469.749 341.473L508.749 318.926Z" fill="white" stroke="#EDE9FE"/>
<path d="M591.749 318.926C592.523 318.478 593.477 318.478 594.251 318.926L633.251 341.473C634.024 341.92 634.5 342.744 634.5 343.637V388.738C634.5 389.631 634.024 390.455 633.251 390.902L594.251 413.449C593.477 413.897 592.523 413.897 591.749 413.449L552.749 390.902C551.976 390.455 551.5 389.631 551.5 388.738V343.637C551.5 342.744 551.976 341.92 552.749 341.473L591.749 318.926Z" fill="white" stroke="#EDE9FE"/>
<path d="M674.749 318.926C675.523 318.478 676.477 318.478 677.251 318.926L716.251 341.473C717.024 341.92 717.5 342.744 717.5 343.637V388.738C717.5 389.631 717.024 390.455 716.251 390.902L677.251 413.449C676.477 413.897 675.523 413.897 674.749 413.449L635.749 390.902C634.976 390.455 634.5 389.631 634.5 388.738V343.637C634.5 342.744 634.976 341.92 635.749 341.473L674.749 318.926Z" fill="white" stroke="#EDE9FE"/>
<path d="M757.749 318.926C758.523 318.478 759.477 318.478 760.251 318.926L799.251 341.473C800.024 341.92 800.5 342.744 800.5 343.637V388.738C800.5 389.631 800.024 390.455 799.251 390.902L760.251 413.449C759.477 413.897 758.523 413.897 757.749 413.449L718.749 390.902C717.976 390.455 717.5 389.631 717.5 388.738V343.637C717.5 342.744 717.976 341.92 718.749 341.473L757.749 318.926Z" fill="white" stroke="#EDE9FE"/>
<path d="M840.749 318.926C841.523 318.478 842.477 318.478 843.251 318.926L882.251 341.473C883.024 341.92 883.5 342.744 883.5 343.637V388.738C883.5 389.631 883.024 390.455 882.251 390.902L843.251 413.449C842.477 413.897 841.523 413.897 840.749 413.449L801.749 390.902C800.976 390.455 800.5 389.631 800.5 388.738V343.637C800.5 342.744 800.976 341.92 801.749 341.473L840.749 318.926Z" fill="#F5F3FF" stroke="#EDE9FE"/>
<path d="M923.749 318.926C924.523 318.478 925.477 318.478 926.251 318.926L965.251 341.473C966.024 341.92 966.5 342.744 966.5 343.637V388.738C966.5 389.631 966.024 390.455 965.251 390.902L926.251 413.449C925.477 413.897 924.523 413.897 923.749 413.449L884.749 390.902C883.976 390.455 883.5 389.631 883.5 388.738V343.637C883.5 342.744 883.976 341.92 884.749 341.473L923.749 318.926Z" fill="#F5F3FF" stroke="#EDE9FE"/>
<path d="M1006.75 318.926C1007.52 318.478 1008.48 318.478 1009.25 318.926L1048.25 341.473C1049.02 341.92 1049.5 342.744 1049.5 343.637V388.738C1049.5 389.631 1049.02 390.455 1048.25 390.902L1009.25 413.449C1008.48 413.897 1007.52 413.897 1006.75 413.449L967.749 390.902C966.976 390.455 966.5 389.631 966.5 388.738V343.637C966.5 342.744 966.976 341.92 967.749 341.473L1006.75 318.926Z" fill="white" stroke="#EDE9FE"/>
<path d="M1089.75 318.926C1090.52 318.478 1091.48 318.478 1092.25 318.926L1131.25 341.473C1132.02 341.92 1132.5 342.744 1132.5 343.637V388.738C1132.5 389.631 1132.02 390.455 1131.25 390.902L1092.25 413.449C1091.48 413.897 1090.52 413.897 1089.75 413.449L1050.75 390.902C1049.98 390.455 1049.5 389.631 1049.5 388.738V343.637C1049.5 342.744 1049.98 341.92 1050.75 341.473L1089.75 318.926Z" fill="white" stroke="#EDE9FE"/>
<path d="M1172.75 318.926C1173.52 318.478 1174.48 318.478 1175.25 318.926L1214.25 341.473C1215.02 341.92 1215.5 342.744 1215.5 343.637V388.738C1215.5 389.631 1215.02 390.455 1214.25 390.902L1175.25 413.449C1174.48 413.897 1173.52 413.897 1172.75 413.449L1133.75 390.902C1132.98 390.455 1132.5 389.631 1132.5 388.738V343.637C1132.5 342.744 1132.98 341.92 1133.75 341.473L1172.75 318.926Z" fill="white" stroke="#EDE9FE"/>
<path d="M1255.75 318.926C1256.52 318.478 1257.48 318.478 1258.25 318.926L1297.25 341.473C1298.02 341.92 1298.5 342.744 1298.5 343.637V388.738C1298.5 389.631 1298.02 390.455 1297.25 390.902L1258.25 413.449C1257.48 413.897 1256.52 413.897 1255.75 413.449L1216.75 390.902C1215.98 390.455 1215.5 389.631 1215.5 388.738V343.637C1215.5 342.744 1215.98 341.92 1216.75 341.473L1255.75 318.926Z" fill="white" stroke="#EDE9FE"/>
<path d="M1338.75 318.926C1339.52 318.478 1340.48 318.478 1341.25 318.926L1380.25 341.473C1381.02 341.92 1381.5 342.744 1381.5 343.637V388.738C1381.5 389.631 1381.02 390.455 1380.25 390.902L1341.25 413.449C1340.48 413.897 1339.52 413.897 1338.75 413.449L1299.75 390.902C1298.98 390.455 1298.5 389.631 1298.5 388.738V343.637C1298.5 342.744 1298.98 341.92 1299.75 341.473L1338.75 318.926Z" fill="white" stroke="#EDE9FE"/>
<path d="M1421.75 318.926C1422.52 318.478 1423.48 318.478 1424.25 318.926L1463.25 341.473C1464.02 341.92 1464.5 342.744 1464.5 343.637V388.738C1464.5 389.631 1464.02 390.455 1463.25 390.902L1424.25 413.449C1423.48 413.897 1422.52 413.897 1421.75 413.449L1382.75 390.902C1381.98 390.455 1381.5 389.631 1381.5 388.738V343.637C1381.5 342.744 1381.98 341.92 1382.75 341.473L1421.75 318.926Z" fill="white" stroke="#EDE9FE"/>
<path d="M52.2487 390.051C53.0229 389.603 53.9771 389.603 54.7513 390.051L93.7513 412.598C94.524 413.045 95 413.869 95 414.762V459.863C95 460.756 94.524 461.58 93.7513 462.027L54.7513 484.574C53.9771 485.022 53.0229 485.022 52.2487 484.574L13.2487 462.027C12.4759 461.58 12 460.756 12 459.863V414.762C12 413.869 12.4759 413.045 13.2487 412.598L52.2487 390.051Z" fill="white" stroke="#EDE9FE"/>
<path d="M135.249 390.051C136.023 389.603 136.977 389.603 137.751 390.051L176.751 412.598C177.524 413.045 178 413.869 178 414.762V459.863C178 460.756 177.524 461.58 176.751 462.027L137.751 484.574C136.977 485.022 136.023 485.022 135.249 484.574L96.2487 462.027C95.4759 461.58 95 460.756 95 459.863V414.762C95 413.869 95.4759 413.045 96.2487 412.598L135.249 390.051Z" fill="white" stroke="#EDE9FE"/>
<path d="M218.249 390.051C219.023 389.603 219.977 389.603 220.751 390.051L259.751 412.598C260.524 413.045 261 413.869 261 414.762V459.863C261 460.756 260.524 461.58 259.751 462.027L220.751 484.574C219.977 485.022 219.023 485.022 218.249 484.574L179.249 462.027C178.476 461.58 178 460.756 178 459.863V414.762C178 413.869 178.476 413.045 179.249 412.598L218.249 390.051Z" fill="white" stroke="#EDE9FE"/>
<path d="M301.249 390.051C302.023 389.603 302.977 389.603 303.751 390.051L342.751 412.598C343.524 413.045 344 413.869 344 414.762V459.863C344 460.756 343.524 461.58 342.751 462.027L303.751 484.574C302.977 485.022 302.023 485.022 301.249 484.574L262.249 462.027C261.476 461.58 261 460.756 261 459.863V414.762C261 413.869 261.476 413.045 262.249 412.598L301.249 390.051Z" fill="white" stroke="#EDE9FE"/>
<path d="M384.249 390.051C385.023 389.603 385.977 389.603 386.751 390.051L425.751 412.598C426.524 413.045 427 413.869 427 414.762V459.863C427 460.756 426.524 461.58 425.751 462.027L386.751 484.574C385.977 485.022 385.023 485.022 384.249 484.574L345.249 462.027C344.476 461.58 344 460.756 344 459.863V414.762C344 413.869 344.476 413.045 345.249 412.598L384.249 390.051Z" fill="white" stroke="#EDE9FE"/>
<path d="M467.249 390.051C468.023 389.603 468.977 389.603 469.751 390.051L508.751 412.598C509.524 413.045 510 413.869 510 414.762V459.863C510 460.756 509.524 461.58 508.751 462.027L469.751 484.574C468.977 485.022 468.023 485.022 467.249 484.574L428.249 462.027C427.476 461.58 427 460.756 427 459.863V414.762C427 413.869 427.476 413.045 428.249 412.598L467.249 390.051Z" fill="white" stroke="#EDE9FE"/>
<path d="M550.249 390.051C551.023 389.603 551.977 389.603 552.751 390.051L591.751 412.598C592.524 413.045 593 413.869 593 414.762V459.863C593 460.756 592.524 461.58 591.751 462.027L552.751 484.574C551.977 485.022 551.023 485.022 550.249 484.574L511.249 462.027C510.476 461.58 510 460.756 510 459.863V414.762C510 413.869 510.476 413.045 511.249 412.598L550.249 390.051Z" fill="white" stroke="#EDE9FE"/>
<path d="M633.249 390.051C634.023 389.603 634.977 389.603 635.751 390.051L674.751 412.598C675.524 413.045 676 413.869 676 414.762V459.863C676 460.756 675.524 461.58 674.751 462.027L635.751 484.574C634.977 485.022 634.023 485.022 633.249 484.574L594.249 462.027C593.476 461.58 593 460.756 593 459.863V414.762C593 413.869 593.476 413.045 594.249 412.598L633.249 390.051Z" fill="white" stroke="#EDE9FE"/>
<path d="M716.249 390.051C717.023 389.603 717.977 389.603 718.751 390.051L757.751 412.598C758.524 413.045 759 413.869 759 414.762V459.863C759 460.756 758.524 461.58 757.751 462.027L718.751 484.574C717.977 485.022 717.023 485.022 716.249 484.574L677.249 462.027C676.476 461.58 676 460.756 676 459.863V414.762C676 413.869 676.476 413.045 677.249 412.598L716.249 390.051Z" fill="white" stroke="#EDE9FE"/>
<path d="M799.249 390.051C800.023 389.603 800.977 389.603 801.751 390.051L840.751 412.598C841.524 413.045 842 413.869 842 414.762V459.863C842 460.756 841.524 461.58 840.751 462.027L801.751 484.574C800.977 485.022 800.023 485.022 799.249 484.574L760.249 462.027C759.476 461.58 759 460.756 759 459.863V414.762C759 413.869 759.476 413.045 760.249 412.598L799.249 390.051Z" fill="white" stroke="#EDE9FE"/>
<path d="M882.249 390.051C883.023 389.603 883.977 389.603 884.751 390.051L923.751 412.598C924.524 413.045 925 413.869 925 414.762V459.863C925 460.756 924.524 461.58 923.751 462.027L884.751 484.574C883.977 485.022 883.023 485.022 882.249 484.574L843.249 462.027C842.476 461.58 842 460.756 842 459.863V414.762C842 413.869 842.476 413.045 843.249 412.598L882.249 390.051Z" fill="white" stroke="#EDE9FE"/>
<path d="M965.249 390.051C966.023 389.603 966.977 389.603 967.751 390.051L1006.75 412.598C1007.52 413.045 1008 413.869 1008 414.762V459.863C1008 460.756 1007.52 461.58 1006.75 462.027L967.751 484.574C966.977 485.022 966.023 485.022 965.249 484.574L926.249 462.027C925.476 461.58 925 460.756 925 459.863V414.762C925 413.869 925.476 413.045 926.249 412.598L965.249 390.051Z" fill="white" stroke="#EDE9FE"/>
<path d="M1048.25 390.051C1049.02 389.603 1049.98 389.603 1050.75 390.051L1089.75 412.598C1090.52 413.045 1091 413.869 1091 414.762V459.863C1091 460.756 1090.52 461.58 1089.75 462.027L1050.75 484.574C1049.98 485.022 1049.02 485.022 1048.25 484.574L1009.25 462.027C1008.48 461.58 1008 460.756 1008 459.863V414.762C1008 413.869 1008.48 413.045 1009.25 412.598L1048.25 390.051Z" fill="white" stroke="#EDE9FE"/>
<path d="M1131.25 390.051C1132.02 389.603 1132.98 389.603 1133.75 390.051L1172.75 412.598C1173.52 413.045 1174 413.869 1174 414.762V459.863C1174 460.756 1173.52 461.58 1172.75 462.027L1133.75 484.574C1132.98 485.022 1132.02 485.022 1131.25 484.574L1092.25 462.027C1091.48 461.58 1091 460.756 1091 459.863V414.762C1091 413.869 1091.48 413.045 1092.25 412.598L1131.25 390.051Z" fill="white" stroke="#EDE9FE"/>
<path d="M1214.25 390.051C1215.02 389.603 1215.98 389.603 1216.75 390.051L1255.75 412.598C1256.52 413.045 1257 413.869 1257 414.762V459.863C1257 460.756 1256.52 461.58 1255.75 462.027L1216.75 484.574C1215.98 485.022 1215.02 485.022 1214.25 484.574L1175.25 462.027C1174.48 461.58 1174 460.756 1174 459.863V414.762C1174 413.869 1174.48 413.045 1175.25 412.598L1214.25 390.051Z" fill="white" stroke="#EDE9FE"/>
<path d="M1297.25 390.051C1298.02 389.603 1298.98 389.603 1299.75 390.051L1338.75 412.598C1339.52 413.045 1340 413.869 1340 414.762V459.863C1340 460.756 1339.52 461.58 1338.75 462.027L1299.75 484.574C1298.98 485.022 1298.02 485.022 1297.25 484.574L1258.25 462.027C1257.48 461.58 1257 460.756 1257 459.863V414.762C1257 413.869 1257.48 413.045 1258.25 412.598L1297.25 390.051Z" fill="white" stroke="#EDE9FE"/>
<path d="M1380.25 390.051C1381.02 389.603 1381.98 389.603 1382.75 390.051L1421.75 412.598C1422.52 413.045 1423 413.869 1423 414.762V459.863C1423 460.756 1422.52 461.58 1421.75 462.027L1382.75 484.574C1381.98 485.022 1381.02 485.022 1380.25 484.574L1341.25 462.027C1340.48 461.58 1340 460.756 1340 459.863V414.762C1340 413.869 1340.48 413.045 1341.25 412.598L1380.25 390.051Z" fill="white" stroke="#EDE9FE"/>
<path d="M1463.25 390.051C1464.02 389.603 1464.98 389.603 1465.75 390.051L1504.75 412.598C1505.52 413.045 1506 413.869 1506 414.762V459.863C1506 460.756 1505.52 461.58 1504.75 462.027L1465.75 484.574C1464.98 485.022 1464.02 485.022 1463.25 484.574L1424.25 462.027C1423.48 461.58 1423 460.756 1423 459.863V414.762C1423 413.869 1423.48 413.045 1424.25 412.598L1463.25 390.051Z" fill="white" stroke="#EDE9FE"/>
<path d="M10.7487 461.176C11.5229 460.728 12.4771 460.728 13.2513 461.176L52.2513 483.723C53.024 484.17 53.5 484.994 53.5 485.887V530.988C53.5 531.881 53.024 532.705 52.2513 533.152L13.2513 555.699C12.4771 556.147 11.5229 556.147 10.7487 555.699L-28.2513 533.152C-29.0241 532.705 -29.5 531.881 -29.5 530.988V485.887C-29.5 484.994 -29.0241 484.17 -28.2513 483.723L10.7487 461.176Z" fill="white" stroke="#EDE9FE"/>
<path d="M93.7487 461.176C94.5229 460.728 95.4771 460.728 96.2513 461.176L135.251 483.723C136.024 484.17 136.5 484.994 136.5 485.887V530.988C136.5 531.881 136.024 532.705 135.251 533.152L96.2513 555.699C95.4771 556.147 94.5229 556.147 93.7487 555.699L54.7487 533.152C53.9759 532.705 53.5 531.881 53.5 530.988V485.887C53.5 484.994 53.9759 484.17 54.7487 483.723L93.7487 461.176Z" fill="white" stroke="#EDE9FE"/>
<path d="M176.749 461.176C177.523 460.728 178.477 460.728 179.251 461.176L218.251 483.723C219.024 484.17 219.5 484.994 219.5 485.887V530.988C219.5 531.881 219.024 532.705 218.251 533.152L179.251 555.699C178.477 556.147 177.523 556.147 176.749 555.699L137.749 533.152C136.976 532.705 136.5 531.881 136.5 530.988V485.887C136.5 484.994 136.976 484.17 137.749 483.723L176.749 461.176Z" fill="white" stroke="#EDE9FE"/>
<path d="M259.749 461.176C260.523 460.728 261.477 460.728 262.251 461.176L301.251 483.723C302.024 484.17 302.5 484.994 302.5 485.887V530.988C302.5 531.881 302.024 532.705 301.251 533.152L262.251 555.699C261.477 556.147 260.523 556.147 259.749 555.699L220.749 533.152C219.976 532.705 219.5 531.881 219.5 530.988V485.887C219.5 484.994 219.976 484.17 220.749 483.723L259.749 461.176Z" fill="white" stroke="#EDE9FE"/>
<path d="M342.749 461.176C343.523 460.728 344.477 460.728 345.251 461.176L384.251 483.723C385.024 484.17 385.5 484.994 385.5 485.887V530.988C385.5 531.881 385.024 532.705 384.251 533.152L345.251 555.699C344.477 556.147 343.523 556.147 342.749 555.699L303.749 533.152C302.976 532.705 302.5 531.881 302.5 530.988V485.887C302.5 484.994 302.976 484.17 303.749 483.723L342.749 461.176Z" fill="white" stroke="#EDE9FE"/>
<path d="M425.749 461.176C426.523 460.728 427.477 460.728 428.251 461.176L467.251 483.723C468.024 484.17 468.5 484.994 468.5 485.887V530.988C468.5 531.881 468.024 532.705 467.251 533.152L428.251 555.699C427.477 556.147 426.523 556.147 425.749 555.699L386.749 533.152C385.976 532.705 385.5 531.881 385.5 530.988V485.887C385.5 484.994 385.976 484.17 386.749 483.723L425.749 461.176Z" fill="white" stroke="#EDE9FE"/>
<path d="M508.749 461.176C509.523 460.728 510.477 460.728 511.251 461.176L550.251 483.723C551.024 484.17 551.5 484.994 551.5 485.887V530.988C551.5 531.881 551.024 532.705 550.251 533.152L511.251 555.699C510.477 556.147 509.523 556.147 508.749 555.699L469.749 533.152C468.976 532.705 468.5 531.881 468.5 530.988V485.887C468.5 484.994 468.976 484.17 469.749 483.723L508.749 461.176Z" fill="white" stroke="#EDE9FE"/>
<path d="M591.749 461.176C592.523 460.728 593.477 460.728 594.251 461.176L633.251 483.723C634.024 484.17 634.5 484.994 634.5 485.887V530.988C634.5 531.881 634.024 532.705 633.251 533.152L594.251 555.699C593.477 556.147 592.523 556.147 591.749 555.699L552.749 533.152C551.976 532.705 551.5 531.881 551.5 530.988V485.887C551.5 484.994 551.976 484.17 552.749 483.723L591.749 461.176Z" fill="white" stroke="#EDE9FE"/>
<path d="M674.749 461.176C675.523 460.728 676.477 460.728 677.251 461.176L716.251 483.723C717.024 484.17 717.5 484.994 717.5 485.887V530.988C717.5 531.881 717.024 532.705 716.251 533.152L677.251 555.699C676.477 556.147 675.523 556.147 674.749 555.699L635.749 533.152C634.976 532.705 634.5 531.881 634.5 530.988V485.887C634.5 484.994 634.976 484.17 635.749 483.723L674.749 461.176Z" fill="white" stroke="#EDE9FE"/>
<path d="M757.749 461.176C758.523 460.728 759.477 460.728 760.251 461.176L799.251 483.723C800.024 484.17 800.5 484.994 800.5 485.887V530.988C800.5 531.881 800.024 532.705 799.251 533.152L760.251 555.699C759.477 556.147 758.523 556.147 757.749 555.699L718.749 533.152C717.976 532.705 717.5 531.881 717.5 530.988V485.887C717.5 484.994 717.976 484.17 718.749 483.723L757.749 461.176Z" fill="white" stroke="#EDE9FE"/>
<path d="M840.749 461.176C841.523 460.728 842.477 460.728 843.251 461.176L882.251 483.723C883.024 484.17 883.5 484.994 883.5 485.887V530.988C883.5 531.881 883.024 532.705 882.251 533.152L843.251 555.699C842.477 556.147 841.523 556.147 840.749 555.699L801.749 533.152C800.976 532.705 800.5 531.881 800.5 530.988V485.887C800.5 484.994 800.976 484.17 801.749 483.723L840.749 461.176Z" fill="white" stroke="#EDE9FE"/>
<path d="M923.749 461.176C924.523 460.728 925.477 460.728 926.251 461.176L965.251 483.723C966.024 484.17 966.5 484.994 966.5 485.887V530.988C966.5 531.881 966.024 532.705 965.251 533.152L926.251 555.699C925.477 556.147 924.523 556.147 923.749 555.699L884.749 533.152C883.976 532.705 883.5 531.881 883.5 530.988V485.887C883.5 484.994 883.976 484.17 884.749 483.723L923.749 461.176Z" fill="white" stroke="#EDE9FE"/>
<path d="M1006.75 461.176C1007.52 460.728 1008.48 460.728 1009.25 461.176L1048.25 483.723C1049.02 484.17 1049.5 484.994 1049.5 485.887V530.988C1049.5 531.881 1049.02 532.705 1048.25 533.152L1009.25 555.699C1008.48 556.147 1007.52 556.147 1006.75 555.699L967.749 533.152C966.976 532.705 966.5 531.881 966.5 530.988V485.887C966.5 484.994 966.976 484.17 967.749 483.723L1006.75 461.176Z" fill="white" stroke="#EDE9FE"/>
<path d="M1089.75 461.176C1090.52 460.728 1091.48 460.728 1092.25 461.176L1131.25 483.723C1132.02 484.17 1132.5 484.994 1132.5 485.887V530.988C1132.5 531.881 1132.02 532.705 1131.25 533.152L1092.25 555.699C1091.48 556.147 1090.52 556.147 1089.75 555.699L1050.75 533.152C1049.98 532.705 1049.5 531.881 1049.5 530.988V485.887C1049.5 484.994 1049.98 484.17 1050.75 483.723L1089.75 461.176Z" fill="white" stroke="#EDE9FE"/>
<path d="M1172.75 461.176C1173.52 460.728 1174.48 460.728 1175.25 461.176L1214.25 483.723C1215.02 484.17 1215.5 484.994 1215.5 485.887V530.988C1215.5 531.881 1215.02 532.705 1214.25 533.152L1175.25 555.699C1174.48 556.147 1173.52 556.147 1172.75 555.699L1133.75 533.152C1132.98 532.705 1132.5 531.881 1132.5 530.988V485.887C1132.5 484.994 1132.98 484.17 1133.75 483.723L1172.75 461.176Z" fill="white" stroke="#EDE9FE"/>
<path d="M1255.75 461.176C1256.52 460.728 1257.48 460.728 1258.25 461.176L1297.25 483.723C1298.02 484.17 1298.5 484.994 1298.5 485.887V530.988C1298.5 531.881 1298.02 532.705 1297.25 533.152L1258.25 555.699C1257.48 556.147 1256.52 556.147 1255.75 555.699L1216.75 533.152C1215.98 532.705 1215.5 531.881 1215.5 530.988V485.887C1215.5 484.994 1215.98 484.17 1216.75 483.723L1255.75 461.176Z" fill="white" stroke="#EDE9FE"/>
<path d="M1338.75 461.176C1339.52 460.728 1340.48 460.728 1341.25 461.176L1380.25 483.723C1381.02 484.17 1381.5 484.994 1381.5 485.887V530.988C1381.5 531.881 1381.02 532.705 1380.25 533.152L1341.25 555.699C1340.48 556.147 1339.52 556.147 1338.75 555.699L1299.75 533.152C1298.98 532.705 1298.5 531.881 1298.5 530.988V485.887C1298.5 484.994 1298.98 484.17 1299.75 483.723L1338.75 461.176Z" fill="white" stroke="#EDE9FE"/>
<path d="M1421.75 461.176C1422.52 460.728 1423.48 460.728 1424.25 461.176L1463.25 483.723C1464.02 484.17 1464.5 484.994 1464.5 485.887V530.988C1464.5 531.881 1464.02 532.705 1463.25 533.152L1424.25 555.699C1423.48 556.147 1422.52 556.147 1421.75 555.699L1382.75 533.152C1381.98 532.705 1381.5 531.881 1381.5 530.988V485.887C1381.5 484.994 1381.98 484.17 1382.75 483.723L1421.75 461.176Z" fill="white" stroke="#EDE9FE"/>
<path d="M52.2487 532.301C53.0229 531.853 53.9771 531.853 54.7513 532.301L93.7513 554.848C94.524 555.295 95 556.119 95 557.012V602.113C95 603.006 94.524 603.83 93.7513 604.277L54.7513 626.824C53.9771 627.272 53.0229 627.272 52.2487 626.824L13.2487 604.277C12.4759 603.83 12 603.006 12 602.113V557.012C12 556.119 12.4759 555.295 13.2487 554.848L52.2487 532.301Z" fill="white" stroke="#EDE9FE"/>
<path d="M135.249 532.301C136.023 531.853 136.977 531.853 137.751 532.301L176.751 554.848C177.524 555.295 178 556.119 178 557.012V602.113C178 603.006 177.524 603.83 176.751 604.277L137.751 626.824C136.977 627.272 136.023 627.272 135.249 626.824L96.2487 604.277C95.4759 603.83 95 603.006 95 602.113V557.012C95 556.119 95.4759 555.295 96.2487 554.848L135.249 532.301Z" fill="white" stroke="#EDE9FE"/>
<path d="M218.249 532.301C219.023 531.853 219.977 531.853 220.751 532.301L259.751 554.848C260.524 555.295 261 556.119 261 557.012V602.113C261 603.006 260.524 603.83 259.751 604.277L220.751 626.824C219.977 627.272 219.023 627.272 218.249 626.824L179.249 604.277C178.476 603.83 178 603.006 178 602.113V557.012C178 556.119 178.476 555.295 179.249 554.848L218.249 532.301Z" fill="white" stroke="#EDE9FE"/>
<path d="M301.249 532.301C302.023 531.853 302.977 531.853 303.751 532.301L342.751 554.848C343.524 555.295 344 556.119 344 557.012V602.113C344 603.006 343.524 603.83 342.751 604.277L303.751 626.824C302.977 627.272 302.023 627.272 301.249 626.824L262.249 604.277C261.476 603.83 261 603.006 261 602.113V557.012C261 556.119 261.476 555.295 262.249 554.848L301.249 532.301Z" fill="white" stroke="#EDE9FE"/>
<path d="M384.249 532.301C385.023 531.853 385.977 531.853 386.751 532.301L425.751 554.848C426.524 555.295 427 556.119 427 557.012V602.113C427 603.006 426.524 603.83 425.751 604.277L386.751 626.824C385.977 627.272 385.023 627.272 384.249 626.824L345.249 604.277C344.476 603.83 344 603.006 344 602.113V557.012C344 556.119 344.476 555.295 345.249 554.848L384.249 532.301Z" fill="white" stroke="#EDE9FE"/>
<path d="M467.249 532.301C468.023 531.853 468.977 531.853 469.751 532.301L508.751 554.848C509.524 555.295 510 556.119 510 557.012V602.113C510 603.006 509.524 603.83 508.751 604.277L469.751 626.824C468.977 627.272 468.023 627.272 467.249 626.824L428.249 604.277C427.476 603.83 427 603.006 427 602.113V557.012C427 556.119 427.476 555.295 428.249 554.848L467.249 532.301Z" fill="white" stroke="#EDE9FE"/>
<path d="M550.249 532.301C551.023 531.853 551.977 531.853 552.751 532.301L591.751 554.848C592.524 555.295 593 556.119 593 557.012V602.113C593 603.006 592.524 603.83 591.751 604.277L552.751 626.824C551.977 627.272 551.023 627.272 550.249 626.824L511.249 604.277C510.476 603.83 510 603.006 510 602.113V557.012C510 556.119 510.476 555.295 511.249 554.848L550.249 532.301Z" fill="white" stroke="#EDE9FE"/>
<path d="M633.249 532.301C634.023 531.853 634.977 531.853 635.751 532.301L674.751 554.848C675.524 555.295 676 556.119 676 557.012V602.113C676 603.006 675.524 603.83 674.751 604.277L635.751 626.824C634.977 627.272 634.023 627.272 633.249 626.824L594.249 604.277C593.476 603.83 593 603.006 593 602.113V557.012C593 556.119 593.476 555.295 594.249 554.848L633.249 532.301Z" fill="white" stroke="#EDE9FE"/>
<path d="M716.249 532.301C717.023 531.853 717.977 531.853 718.751 532.301L757.751 554.848C758.524 555.295 759 556.119 759 557.012V602.113C759 603.006 758.524 603.83 757.751 604.277L718.751 626.824C717.977 627.272 717.023 627.272 716.249 626.824L677.249 604.277C676.476 603.83 676 603.006 676 602.113V557.012C676 556.119 676.476 555.295 677.249 554.848L716.249 532.301Z" fill="white" stroke="#EDE9FE"/>
<path d="M799.249 532.301C800.023 531.853 800.977 531.853 801.751 532.301L840.751 554.848C841.524 555.295 842 556.119 842 557.012V602.113C842 603.006 841.524 603.83 840.751 604.277L801.751 626.824C800.977 627.272 800.023 627.272 799.249 626.824L760.249 604.277C759.476 603.83 759 603.006 759 602.113V557.012C759 556.119 759.476 555.295 760.249 554.848L799.249 532.301Z" fill="white" stroke="#EDE9FE"/>
<path d="M882.249 532.301C883.023 531.853 883.977 531.853 884.751 532.301L923.751 554.848C924.524 555.295 925 556.119 925 557.012V602.113C925 603.006 924.524 603.83 923.751 604.277L884.751 626.824C883.977 627.272 883.023 627.272 882.249 626.824L843.249 604.277C842.476 603.83 842 603.006 842 602.113V557.012C842 556.119 842.476 555.295 843.249 554.848L882.249 532.301Z" fill="white" stroke="#EDE9FE"/>
<path d="M965.249 532.301C966.023 531.853 966.977 531.853 967.751 532.301L1006.75 554.848C1007.52 555.295 1008 556.119 1008 557.012V602.113C1008 603.006 1007.52 603.83 1006.75 604.277L967.751 626.824C966.977 627.272 966.023 627.272 965.249 626.824L926.249 604.277C925.476 603.83 925 603.006 925 602.113V557.012C925 556.119 925.476 555.295 926.249 554.848L965.249 532.301Z" fill="white" stroke="#EDE9FE"/>
<path d="M1048.25 532.301C1049.02 531.853 1049.98 531.853 1050.75 532.301L1089.75 554.848C1090.52 555.295 1091 556.119 1091 557.012V602.113C1091 603.006 1090.52 603.83 1089.75 604.277L1050.75 626.824C1049.98 627.272 1049.02 627.272 1048.25 626.824L1009.25 604.277C1008.48 603.83 1008 603.006 1008 602.113V557.012C1008 556.119 1008.48 555.295 1009.25 554.848L1048.25 532.301Z" fill="white" stroke="#EDE9FE"/>
<path d="M1131.25 532.301C1132.02 531.853 1132.98 531.853 1133.75 532.301L1172.75 554.848C1173.52 555.295 1174 556.119 1174 557.012V602.113C1174 603.006 1173.52 603.83 1172.75 604.277L1133.75 626.824C1132.98 627.272 1132.02 627.272 1131.25 626.824L1092.25 604.277C1091.48 603.83 1091 603.006 1091 602.113V557.012C1091 556.119 1091.48 555.295 1092.25 554.848L1131.25 532.301Z" fill="white" stroke="#EDE9FE"/>
<path d="M1214.25 532.301C1215.02 531.853 1215.98 531.853 1216.75 532.301L1255.75 554.848C1256.52 555.295 1257 556.119 1257 557.012V602.113C1257 603.006 1256.52 603.83 1255.75 604.277L1216.75 626.824C1215.98 627.272 1215.02 627.272 1214.25 626.824L1175.25 604.277C1174.48 603.83 1174 603.006 1174 602.113V557.012C1174 556.119 1174.48 555.295 1175.25 554.848L1214.25 532.301Z" fill="white" stroke="#EDE9FE"/>
<path d="M1297.25 532.301C1298.02 531.853 1298.98 531.853 1299.75 532.301L1338.75 554.848C1339.52 555.295 1340 556.119 1340 557.012V602.113C1340 603.006 1339.52 603.83 1338.75 604.277L1299.75 626.824C1298.98 627.272 1298.02 627.272 1297.25 626.824L1258.25 604.277C1257.48 603.83 1257 603.006 1257 602.113V557.012C1257 556.119 1257.48 555.295 1258.25 554.848L1297.25 532.301Z" fill="white" stroke="#EDE9FE"/>
<path d="M1380.25 532.301C1381.02 531.853 1381.98 531.853 1382.75 532.301L1421.75 554.848C1422.52 555.295 1423 556.119 1423 557.012V602.113C1423 603.006 1422.52 603.83 1421.75 604.277L1382.75 626.824C1381.98 627.272 1381.02 627.272 1380.25 626.824L1341.25 604.277C1340.48 603.83 1340 603.006 1340 602.113V557.012C1340 556.119 1340.48 555.295 1341.25 554.848L1380.25 532.301Z" fill="white" stroke="#EDE9FE"/>
<path d="M1463.25 532.301C1464.02 531.853 1464.98 531.853 1465.75 532.301L1504.75 554.848C1505.52 555.295 1506 556.119 1506 557.012V602.113C1506 603.006 1505.52 603.83 1504.75 604.277L1465.75 626.824C1464.98 627.272 1464.02 627.272 1463.25 626.824L1424.25 604.277C1423.48 603.83 1423 603.006 1423 602.113V557.012C1423 556.119 1423.48 555.295 1424.25 554.848L1463.25 532.301Z" fill="white" stroke="#EDE9FE"/>
</g>
<defs>
<linearGradient id="paint0_linear_6907_192562" x1="726.5" y1="-38" x2="726.5" y2="592" gradientUnits="userSpaceOnUse">
<stop stop-color="#2563EB"/>
<stop offset="0.706076" stop-color="#6246EC" stop-opacity="0"/>
<stop offset="1" stop-color="#7C3AED" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 66 KiB

View File

@ -0,0 +1,9 @@
import { component$ } from '@builder.io/qwik';
export const Background = component$(() => {
return (
<div class="absolute inset-0 -z-10 h-full w-full bg-white dark:bg-slate-900 bg-[linear-gradient(to_right,#8080800a_1px,transparent_1px),linear-gradient(to_bottom,#8080800a_1px,transparent_1px)] bg-[size:14px_24px]">
<div class="absolute left-0 right-0 top-0 -z-10 m-auto h-[310px] w-[310px] rounded-full bg-fuchsia-400 opacity-20 blur-[100px]"></div>
</div>
);
});

View File

@ -39,8 +39,12 @@ export const CountriesChart = component$((props: CountriesChartProps) => {
};
const filteredData = locations.value.filter((item) => {
const country = countryLookup.byIso(getCountryCode(item));
return country && country.country.toLowerCase().includes(searchQuery.value.toLowerCase());
try {
const country = countryLookup.byIso(getCountryCode(item));
return country && country.country.toLowerCase().includes(searchQuery.value.toLowerCase());
} catch (e) {
// Do nothing
}
});
return (

View File

@ -5,17 +5,20 @@ import { useToaster } from '../../toaster/toaster';
export const DELETE_MODAL_ID = 'delete-modal';
export const DELETE_CONFIRMATION = 'DELETE';
export const CANCEL_CONFIRMATION = 'CANCEL';
export const RESUME_CONFIRMATION = 'RESUME';
export interface DeleteModalProps {
export interface ModalProps {
id: string;
confirmation: string;
idToDelete?: string;
type: string;
action: ActionStore<any, any>;
operationType: 'delete' | 'cancel' | 'resume';
onSubmitHandler?: () => void;
}
export const DeleteModal = component$(({ id, type, confirmation, idToDelete, onSubmitHandler, action }: DeleteModalProps) => {
export const GenericModal = component$(({ id, type, confirmation, idToDelete, onSubmitHandler, action, operationType }: ModalProps) => {
const inputValue = useSignal('');
const toaster = useToaster();
@ -27,6 +30,30 @@ export const DeleteModal = component$(({ id, type, confirmation, idToDelete, onS
}
});
let successMessage: string;
switch (operationType) {
case 'delete':
successMessage = `Your ${type} has been deleted successfully.`;
break;
case 'cancel':
successMessage = `Your ${type} has been canceled successfully.`;
break;
case 'resume':
successMessage = `Your ${type} has been resumed successfully.`;
break;
}
const operationText = operationType === 'delete' ? 'delete' : operationType === 'cancel' ? 'cancel' : 'resume';
const errorMessage = `Something went wrong while ${operationText}ing your ${type}. Please try again later.`;
const confirmationMessage =
operationType === 'delete'
? `This action cannot be undone. This will permanently delete your ${type}.`
: operationType === 'cancel'
? `You will still be able to use your ${type} until the end of the current billing period.`
: `This will revert your scheduled cancellation and your subscription will remain active.`;
const buttonClass = operationType === 'delete' ? 'btn-error' : operationType === 'cancel' ? 'btn-error' : 'btn-warning';
return (
<>
<dialog id={id} class="modal">
@ -40,7 +67,7 @@ export const DeleteModal = component$(({ id, type, confirmation, idToDelete, onS
if (action.value?.failed && action.value.message) {
toaster.add({
title: 'Error',
description: `Something went wrong while deleting your ${type}. Please try again later.`,
description: errorMessage,
type: 'error',
});
return;
@ -48,7 +75,7 @@ export const DeleteModal = component$(({ id, type, confirmation, idToDelete, onS
toaster.add({
title: 'Success',
description: `Your ${type} has been deleted successfully.`,
description: successMessage,
type: 'info',
});
@ -75,8 +102,7 @@ export const DeleteModal = component$(({ id, type, confirmation, idToDelete, onS
<div class="px-4 md:p-5">
<div class="pt-1 text-left">
<p class="dark:text-gray-300">
You are about to delete your {type}. This action cannot be undone. This will permanently delete your {type} and all of its
data associated with it.
You are about to {operationText} your {type}. {confirmationMessage}
</p>
<label class="label">
<span class="label-text dark:text-gray-500">
@ -102,8 +128,14 @@ export const DeleteModal = component$(({ id, type, confirmation, idToDelete, onS
</label>
)}
</div>
<button type="submit" class="btn btn-error w-full mt-5">
{action.isRunning ? <span class="loading loading-spinner-small"></span> : <span>I understand, delete my {type}</span>}
<button type="submit" class={`btn ${buttonClass} w-full mt-5 ${action.isRunning ? 'btn-disabled' : ''}`}>
{action.isRunning ? (
<span class="loading loading-spinner-small"></span>
) : (
<span>
I understand, {operationText} my {type}
</span>
)}
</button>
</div>
</Form>

View File

@ -9,12 +9,14 @@ import { UNKNOWN_FAVICON } from '../../../temporary-links/utils';
import { useDebouncer } from '../../../../utils/debouncer';
import { LuEye, LuEyeOff, LuDices } from '@qwikest/icons/lucide';
import { sleep } from '@reduced.to/utils';
import { useGetCurrentUser } from '../../../../../../frontend/src/routes/layout';
import { ConditionalWrapper, getRequiredFeatureLevel } from '../../plan-wrapper';
export const LINK_MODAL_ID = 'link-modal';
interface CreateLinkInput {
url: string;
key: string;
key?: string;
expirationTime?: string | number;
passwordProtection?: string;
@ -41,11 +43,26 @@ const CreateLinkInputSchema = z
}),
key: z
.string()
.min(4, { message: 'The short link must be at least 4 characters long.' })
.max(20, { message: 'The short link cannot exceed 20 characters.' })
.regex(/^[a-zA-Z0-9-]*$/, {
message: 'The short link can only contain letters, numbers, and dashes.',
}),
})
.optional()
.refine(
(val) => {
if (!val?.length) {
return true;
}
if (val?.length && val?.length < 4) {
return false;
}
return true;
},
{
message: 'The short link must be at least 4 characters long.',
}
),
expirationTime: z.string().optional(),
expirationTimeToggle: z.string().optional(),
passwordProtection: z
@ -112,7 +129,7 @@ const useCreateLink = globalAction$(
const body: CreateLinkInput = {
url: normalizeUrl(url),
key,
...(key && { key: key }),
...(expirationTime && { expirationTime: new Date(expirationTime).getTime() }),
...(passwordProtection && { password: passwordProtection }),
@ -134,10 +151,10 @@ const useCreateLink = globalAction$(
body: JSON.stringify(body),
});
const data: { url: string; key: string; message?: string[]; statusCode?: number } = await response.json();
const data: { url: string; key: string; message?: string[] } = await response.json();
if (response.status !== 201) {
return fail(data?.statusCode || 500, {
return fail(500, {
message: data?.message || 'There was an error creating your link. Please try again.',
});
}
@ -172,10 +189,14 @@ const initValues = {
utm_content: undefined,
};
export const LinkModal = component$(({ onSubmitHandler }: LinkModalProps) => {
const user = useGetCurrentUser();
const inputValue = useSignal<CreateLinkInput>({ ...initValues });
const faviconUrl = useSignal<string | null>(null);
const previewUrl = useSignal<string | null>(null);
// Short key input field
const requiredLevelToCustomShortLink = useSignal<null | string>(getRequiredFeatureLevel(user.value?.plan || 'FREE', 'CUSTOM_SHORT_KEY'));
// Optional fields
const isExpirationTimeOpen = useSignal(false);
const isPasswordProtectionOpen = useSignal(false);
@ -195,7 +216,7 @@ export const LinkModal = component$(({ onSubmitHandler }: LinkModalProps) => {
);
const generateRandomKey = $(async () => {
if (isGeneratingRandomKey.value) {
if (requiredLevelToCustomShortLink.value || isGeneratingRandomKey.value) {
return;
}
@ -207,28 +228,18 @@ export const LinkModal = component$(({ onSubmitHandler }: LinkModalProps) => {
isGeneratingRandomKey.value = false;
});
const toggleOption = $(
(signal: Signal<boolean>, resetKey: keyof CreateLinkInput | (keyof CreateLinkInput)[], resetValue: any = undefined) => {
signal.value = !signal.value;
if (!signal.value && resetKey !== undefined) {
// Reset field errors
if (Array.isArray(resetKey)) {
resetKey.forEach((key) => {
inputValue.value[key] = resetValue;
if (action.value?.fieldErrors![key]) {
action.value.fieldErrors[key] = [];
}
});
} else {
if (action.value?.fieldErrors![resetKey]) {
action.value.fieldErrors[resetKey] = [];
}
inputValue.value[resetKey] = resetValue;
const toggleOption = $((signal: Signal<boolean>, resetKeys: (keyof CreateLinkInput)[], resetValue: any = undefined) => {
signal.value = !signal.value;
if (!signal.value && resetKeys !== undefined) {
// Reset field errors
resetKeys.forEach((key) => {
inputValue.value[key] = resetValue;
if (action.value?.fieldErrors![key]) {
action.value.fieldErrors[key] = [];
}
}
});
}
);
});
const toggleShowPassword = $(() => {
showPassword.value = !showPassword.value;
@ -295,7 +306,7 @@ export const LinkModal = component$(({ onSubmitHandler }: LinkModalProps) => {
debounceUrlInput(inputValue.value.url);
// if the user is typing nad there is no key, generate one
if (inputValue.value.url.length > 0 && inputValue.value.key.length === 0) {
if (inputValue.value.url.length > 0 && inputValue.value.key?.length === 0) {
generateRandomKey();
}
}}
@ -312,25 +323,29 @@ export const LinkModal = component$(({ onSubmitHandler }: LinkModalProps) => {
<label class="label">
<span class="label-text">Short link</span>
</label>
<div class="tooltip tooltip-left text-sm" data-tip="Generate a random key">
<div class="mt-2 mr-1 text-gray-500">
{isGeneratingRandomKey.value ? (
<span class="loading loading-spinner-small h-5 w-5" />
) : (
<LuDices class="hover:text-gray-700 cursor-pointer h-5 w-5" onClick$={generateRandomKey} />
)}
{requiredLevelToCustomShortLink.value ? (
<ConditionalWrapper access="CUSTOM_SHORT_KEY" cs="mr-[1.7rem]" />
) : (
<div class="tooltip tooltip-left text-sm" data-tip="Generate a random key">
<div class="mt-2 mr-1 text-gray-500">
{isGeneratingRandomKey.value ? (
<span class="loading loading-spinner-small h-5 w-5" />
) : (
<LuDices class={`hover:text-gray-700 cursor-pointer h-5 w-5`} onClick$={generateRandomKey} />
)}
</div>
</div>
</div>
)}
</div>
<div class="join w-full">
<select class="select select-bordered join-item">
<select class={`select select-bordered join-item ${requiredLevelToCustomShortLink.value ? 'select-disabled' : ''}`}>
<option selected>reduced.to</option>
</select>
<div class="w-full">
<input
name="key"
type="text"
class="input input-bordered join-item w-full"
class={`input input-bordered join-item w-full ${requiredLevelToCustomShortLink.value ? 'input-disabled' : ''}`}
placeholder="git"
value={inputValue.value.key}
onInput$={(ev: InputEvent) => {
@ -345,7 +360,6 @@ export const LinkModal = component$(({ onSubmitHandler }: LinkModalProps) => {
</label>
) : null}
</div>
{action.value?.failed && action.value.message && (
<label class="label">
<span class={`label-text text-xs text-error text-left`}>{action.value.message}</span>
@ -356,13 +370,15 @@ export const LinkModal = component$(({ onSubmitHandler }: LinkModalProps) => {
<div class="form-control">
<label class="cursor-pointer label">
<span class="label-text">Expiration date</span>
<input
type="checkbox"
checked={isExpirationTimeOpen.value}
onChange$={() => toggleOption(isExpirationTimeOpen, 'expirationTime', undefined)}
name="expirationTimeToggle"
class="toggle toggle-primary"
/>
<ConditionalWrapper access="LINK_EXPIRATION">
<input
type="checkbox"
checked={isExpirationTimeOpen.value}
onChange$={() => toggleOption(isExpirationTimeOpen, ['expirationTime'], undefined)}
name="expirationTimeToggle"
class="toggle toggle-primary"
/>
</ConditionalWrapper>
</label>
{isExpirationTimeOpen.value && (
<input
@ -385,18 +401,20 @@ export const LinkModal = component$(({ onSubmitHandler }: LinkModalProps) => {
<div class="form-control">
<label class="cursor-pointer label">
<span class="label-text">Password protection</span>
<input
type="checkbox"
checked={isPasswordProtectionOpen.value}
onChange$={() => {
toggleOption(isPasswordProtectionOpen, 'passwordProtection', undefined);
if (!isPasswordProtectionOpen.value) {
showPassword.value = false;
}
}}
name="passwordProtectionToggle"
class="toggle toggle-primary"
/>
<ConditionalWrapper access="PASSWORD_PROTECTION">
<input
type="checkbox"
checked={isPasswordProtectionOpen.value}
onChange$={() => {
toggleOption(isPasswordProtectionOpen, ['passwordProtection'], undefined);
if (!isPasswordProtectionOpen.value) {
showPassword.value = false;
}
}}
name="passwordProtectionToggle"
class="toggle toggle-primary"
/>
</ConditionalWrapper>
</label>
{isPasswordProtectionOpen.value && (
<label class="input input-bordered flex items-center gap-2">
@ -427,19 +445,21 @@ export const LinkModal = component$(({ onSubmitHandler }: LinkModalProps) => {
<div class="form-control">
<label class="cursor-pointer label">
<span class="label-text">UTM Builder</span>
<input
type="checkbox"
checked={isUtmBuilderOpen.value}
onChange$={() => {
toggleOption(
isUtmBuilderOpen,
['utm_ref', 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'],
undefined
);
}}
name="utmBuilderToggle"
class="toggle toggle-primary"
/>
<ConditionalWrapper access="UTM_BUILDER">
<input
type="checkbox"
checked={isUtmBuilderOpen.value}
onChange$={() => {
toggleOption(
isUtmBuilderOpen,
['utm_ref', 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'],
undefined
);
}}
name="utmBuilderToggle"
class="toggle toggle-primary"
/>
</ConditionalWrapper>
</label>
{isUtmBuilderOpen.value && (
<div class="px-4">
@ -577,7 +597,7 @@ export const LinkModal = component$(({ onSubmitHandler }: LinkModalProps) => {
<button
type="submit"
class={`btn btn-primary md:w-full w-1/2 no-animation md:rounded-none m-auto mb-5 md:mb-0 sm:sticky bottom-0 left-0 sm:mt-0 mt-5 ${
inputValue.value.url.length === 0 ? 'cursor-not-allowed btn-disabled' : ''
inputValue.value.url.length === 0 ? '!cursor-not-allowed btn-disabled !bg-opacity-100 !bg-gray-300 dark:!bg-gray-700' : ''
}`}
>
{action.isRunning ? <span class="loading loading-spinner-small"></span> : 'Create'}

View File

@ -1,6 +1,6 @@
import { globalAction$, z, zod$ } from '@builder.io/qwik-city';
import { ACCESS_COOKIE_NAME } from '../../../shared/auth.service';
import { DELETE_CONFIRMATION } from './delete-modal';
import { ACCESS_COOKIE_NAME } from '../../../../shared/auth.service';
import { DELETE_CONFIRMATION } from '../../generic-modal/generic-modal';
export const useDeleteLink = globalAction$(
async ({ idToDelete }, { fail, cookie }) => {
@ -15,7 +15,7 @@ export const useDeleteLink = globalAction$(
const data = await response.json();
if (response.status !== 200) {
return fail(data?.statusCode || 500, {
return fail(500, {
message: data?.message,
});
}

View File

@ -0,0 +1,83 @@
import { component$ } from '@builder.io/qwik';
import { useToaster } from '../../toaster/toaster';
import { ActionStore, Form } from '@builder.io/qwik-city';
import { useRevalidatePlan } from '../../../routes/dashboard/settings/billing/use-revalidate-plan';
export const CONFIRM_MODAL_ID = 'confirm-modal';
interface ConfirmModalProps {
id: string;
planId?: string | null;
priceId?: string | null;
operation?: 'upgrade' | 'downgrade';
action: ActionStore<any, any>;
}
export const ConfirmModal = component$(({ id, planId, priceId, operation = 'upgrade', action }: ConfirmModalProps) => {
const toaster = useToaster();
const revalidatePlan = useRevalidatePlan();
const getModalContent = () => {
switch (operation) {
case 'downgrade':
return {
title: 'Downgrade your subscription?',
description: 'You will be charged the prorated amount immediately, and your subscription will be downgraded.',
successTitle: 'Subscription downgraded',
successDescription: 'You have successfully downgraded your subscription',
errorTitle: 'Could not downgrade your subscription',
};
case 'upgrade':
return {
title: 'Upgrade your subscription?',
description: 'You will be charged the prorated amount immediately, and your subscription will be upgraded.',
successTitle: 'Subscription upgraded',
successDescription: 'You have successfully upgraded your subscription',
errorTitle: 'Could not upgrade your subscription',
};
}
};
const { title, description, successTitle, successDescription, errorTitle } = getModalContent();
return (
<dialog id={id} class="modal">
<Form
action={action}
onSubmitCompleted$={() => {
if (action.status !== 200) {
toaster.add({
title: errorTitle,
description: action.value?.error || 'Try again later',
type: 'error',
});
} else {
toaster.add({
title: successTitle,
description: successDescription,
type: 'info',
});
}
(document.getElementById(id) as any).close();
revalidatePlan.submit();
}}
class="modal-box relative w-full max-w-sm max-h-full"
>
<input type="hidden" name="planId" value={planId} />
<input type="hidden" name="itemId" value={priceId} />
<input type="hidden" name="operationType" value={operation} />
<div class="card card-body !pt-2">
<h3 class="text-lg font-semibold">{title}</h3>
<span class="text-center text-sm font-light">{description}</span>
</div>
<button type="submit" class={`btn btn-primary btn-block ${action.isRunning ? 'btn-disabled' : ''}`}>
{action.isRunning && <span class="loading loading-spinner" />}
Confirm
</button>
</Form>
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>
);
});

View File

@ -0,0 +1,185 @@
import { component$, useSignal, $ } from '@builder.io/qwik';
import { getPlanByPaddleId, PLAN_LEVELS, Plan, FEATURES, FeatureKey } from '@reduced.to/subscription-manager';
import { Form } from '@builder.io/qwik-city';
import { useGetCurrentUser } from '../../../routes/layout';
import { CheckoutEventNames, Paddle } from '@paddle/paddle-js';
import { DARK_THEME, getCurrentTheme } from '../../theme-switcher/theme-switcher';
import { useChangePlan } from './use-change-plan';
import { CONFIRM_MODAL_ID, ConfirmModal } from './confirm-modal';
import { useRevalidatePlan } from '../../../../../frontend/src/routes/dashboard/settings/billing/use-revalidate-plan';
import { LuCheck } from '@qwikest/icons/lucide';
import { CANCEL_PLAN_MODAL_ID, useCancelPlan } from '../../../routes/dashboard/settings/billing/use-cancel-plan';
import { GenericModal } from '../generic-modal/generic-modal';
export const PLAN_MODAL_ID = 'plan-modal';
interface PlanModalProps {
id: string;
paddle?: Paddle;
}
export const PlanModal = component$(({ id, paddle }: PlanModalProps) => {
const changePlanAction = useChangePlan();
const user = useGetCurrentUser();
const revalidatePlan = useRevalidatePlan();
const currentPlan = PLAN_LEVELS[user.value?.plan || 'FREE'];
const filteredPlans = Object.values(PLAN_LEVELS).filter((plan) => plan.YEARLY_PRICE > 0);
const billingCycle = useSignal<'monthly' | 'yearly'>('yearly');
const selectValue = useSignal(filteredPlans[0].PADDLE_PLAN_ID);
const selectedPriceId = useSignal(filteredPlans[0].PADDLE_YEARLY_PRICE_ID);
const selectedPlan = useSignal<Plan>(getPlanByPaddleId(selectValue.value!)!);
const selectedOperation = useSignal<'upgrade' | 'downgrade' | 'cancel'>('upgrade');
const cancelPlanAction = useCancelPlan();
const onChangeSubscription = $((operation: 'upgrade' | 'downgrade' | 'cancel') => {
if (!user.value) {
return;
}
(document.getElementById(id) as any).close();
if (user.value.plan === 'FREE') {
paddle?.Checkout.open({
settings: {
displayMode: 'overlay',
locale: 'en',
theme: getCurrentTheme() === DARK_THEME ? 'dark' : 'light',
},
items: [
{
priceId: selectedPriceId.value!,
quantity: 1,
},
],
customData: {
userId: user.value?.id,
},
});
paddle?.Update({
eventCallback: async (data) => {
if (data.name == CheckoutEventNames.CHECKOUT_COMPLETED) {
setTimeout(revalidatePlan.submit, 2000);
}
},
});
return;
}
if (operation === 'cancel') {
(document.getElementById(CANCEL_PLAN_MODAL_ID) as any).showModal();
return;
}
// If it's not cancel plan, then it's either upgrade or downgrade
selectedOperation.value = operation;
(document.getElementById(CONFIRM_MODAL_ID) as any).showModal();
});
return (
<>
<ConfirmModal
id={CONFIRM_MODAL_ID}
planId={selectedPlan.value.PADDLE_PLAN_ID}
priceId={selectedPriceId.value}
operation={selectedOperation.value as 'upgrade' | 'downgrade'}
action={changePlanAction}
/>
<GenericModal id={CANCEL_PLAN_MODAL_ID} confirmation="CANCEL" operationType="cancel" type="subscription" action={cancelPlanAction} />
<dialog id={id} class="modal">
<Form class="modal-box max-w-lg p-0">
<div class="bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-900 shadow-sm">
<div class="pt-5 px-8">
<div class="flex justify-center items-center mb-2 gap-2">
<img src="/favicon.png" alt="Reduced.to logo" class="w-14 h-14" />
</div>
<h1 class="text-2xl font-semibold text-center dark:text-gray-300">Discover the Perfect Plan</h1>
<p class="text-center text-gray-500 mb-4">Choose the best plan that fits your needs and upgrade for more features.</p>
<div class="px-2">
<div class="flex flex-row gap-x-2">
<select
class="select select-bordered w-7/12"
value={selectValue.value}
onChange$={(event) => {
selectValue.value = (event.target as HTMLSelectElement).value;
selectedPlan.value = getPlanByPaddleId(selectValue.value!)!;
selectedPriceId.value =
billingCycle.value === 'monthly'
? selectedPlan.value.PADDLE_MONTHLY_PRICE_ID!
: selectedPlan.value.PADDLE_YEARLY_PRICE_ID!;
}}
>
{filteredPlans.map((plan) => (
<option key={plan.PADDLE_PLAN_ID} value={plan.PADDLE_PLAN_ID}>
{plan.DISPLAY_NAME}
</option>
))}
</select>
<select
class="select select-bordered w-5/12"
value={billingCycle.value}
onChange$={(event) => {
const value = (event.target as HTMLSelectElement).value;
billingCycle.value = value as 'monthly' | 'yearly';
selectedPriceId.value =
billingCycle.value === 'monthly'
? selectedPlan.value.PADDLE_MONTHLY_PRICE_ID!
: selectedPlan.value.PADDLE_YEARLY_PRICE_ID!;
}}
>
<option value="yearly">Yearly</option>
<option value="monthly">Monthly</option>
</select>
</div>
<h3 class="text-center mt-4 font-semibold dark:text-gray-300">
<span class="badge badge-outline">
{billingCycle.value === 'monthly' ? selectedPlan.value.MONTHLY_PRICE : selectedPlan.value.YEARLY_PRICE}$
</span>
</h3>
<div class="text-xs pb-4 dark:text-gray-400">Payment will be on a {billingCycle.value} basis</div>
</div>
</div>
</div>
<div class="pb-5 px-8 mt-2">
<ul class="py-2 flex flex-col px-2 pl-6 w-full space-y-2">
{Object.entries(selectedPlan.value.FEATURES).map(([key, feature]) => {
const description = FEATURES[key as FeatureKey];
if (!feature.marketingText && !description.displayName) {
return null;
}
return (
<li class="flex space-x-6" key={key}>
<LuCheck class="h-4 w-4 text-blue-600 dark:text-blue-500 mt-0.5" />
<span class="text-gray-800 dark:text-neutral-400">{feature.marketingText || description.displayName}</span>
</li>
);
})}
</ul>
{selectedPlan.value.PADDLE_PLAN_ID !== currentPlan.PADDLE_PLAN_ID ? (
selectedPlan.value.LEVEL > currentPlan.LEVEL ? (
<button class="mt-2 btn btn-primary btn-block" onClick$={() => onChangeSubscription('upgrade')}>
Upgrade
</button>
) : (
<button class="mt-2 btn btn-primary btn-block" onClick$={() => onChangeSubscription('downgrade')}>
Downgrade
</button>
)
) : (
<button class="mt-2 btn btn-error btn-outline btn-block" onClick$={() => onChangeSubscription('cancel')}>
Cancel Plan
</button>
)}
<a href="/pricing" target="_blank" class="link mt-2 block text-center text-sm dark:text-gray-400">
View all plans and features
</a>
</div>
</Form>
<form method="dialog" class="modal-backdrop">
<button onClick$={() => (document.getElementById(id) as any).close()}>close</button>
</form>
</dialog>
</>
);
});

View File

@ -0,0 +1,34 @@
import { globalAction$, z, zod$ } from '@builder.io/qwik-city';
import { setTokensAsCookies } from '../../../shared/auth.service';
export const useChangePlan = globalAction$(
async ({ planId, itemId, operationType }, { fail, cookie }) => {
const response: Response = await fetch(`${process.env.API_DOMAIN}/api/v1/billing/plan`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${cookie.get('accessToken')?.value}`,
},
body: JSON.stringify({ planId, itemId, operationType }),
});
const data = await response.json();
if (response.status !== 201 && response.status !== 200) {
return fail(500, {
message: data?.message,
});
}
const { accessToken, refreshToken } = data;
if (accessToken && refreshToken) {
setTokensAsCookies(accessToken, refreshToken, cookie);
}
return data;
},
zod$({
planId: z.string().min(5),
itemId: z.string().min(5),
operationType: z.string(),
})
);

View File

@ -0,0 +1,59 @@
import { $, component$, Slot } from '@builder.io/qwik';
import { LuBadgeCheck } from '@qwikest/icons/lucide';
import { PLAN_LEVELS, FEATURES } from '@reduced.to/subscription-manager';
import { useGetCurrentUser } from '../../../../frontend/src/routes/layout';
interface ConditionalWrapperProps {
access: keyof typeof FEATURES;
cs?: string;
}
export function getRequiredFeatureLevel(currentPlan: string, access: keyof typeof FEATURES) {
let requiredLevel = '';
for (const planName in PLAN_LEVELS) {
const plan = PLAN_LEVELS[planName];
if (plan.FEATURES[access]?.enabled === true) {
if (currentPlan === planName) return null;
if (!requiredLevel) requiredLevel = plan.DISPLAY_NAME;
}
}
return requiredLevel || 'PRO';
}
export const ConditionalWrapper = component$(({ access, cs }: ConditionalWrapperProps) => {
const user = useGetCurrentUser();
const plan = user?.value?.plan || 'FREE';
const level = getRequiredFeatureLevel(plan, access);
if (!level) {
return <Slot />;
}
const onClick = $((e: Event) => {
e.preventDefault();
});
return (
<div class={`relative border-dark-grey bg-inherit rounded ${cs ? cs : ''}`} onClick$={onClick}>
<div
class={`
tooltip tooltip-left absolute badge badge-ghost z-100 cursor-pointer
left-1/2 transform -translate-x-1/2
p-3 flex items-center`}
data-tip={`This feature is aviailable in the ${level} plan`}
>
<LuBadgeCheck class="mr-2 w-4 h-4" />
<span>{level}</span>
</div>
<div
class="z-10"
preventdefault:click
onClick$={(e) => e.stopPropagation()}
preventdefault:input
onInput$={(e) => e.stopPropagation()}
>
<Slot />
</div>
</div>
);
});

View File

@ -0,0 +1,89 @@
import { $, component$, useSignal } from '@builder.io/qwik';
import { LuChevronDown, LuChevronUp } from '@qwikest/icons/lucide';
export const Faq = component$(() => {
const activeAccordion = useSignal<number | null>(null);
const toggleAccordion = $((index: number) => {
activeAccordion.value = activeAccordion.value === index ? null : index;
});
const faqs = [
{
question: 'Can I cancel my subscription anytime?',
answer: 'Yes, you can cancel your subscription at any time. Your access will remain active until the end of the billing cycle.',
},
{
question: 'How do I track the clicks on my shortened URLs?',
answer:
'Our URL shortener service provides detailed analytics on the number of clicks, geographic location of the clicks, and the referrer. You can access this information from your dashboard.',
},
{
question: 'Is there a limit to how many URLs I can shorten?',
answer:
'The Free plan allows you to shorten up to 5 URLs per month. Our Pro and Business plans offer higher limits of 500 and 2000 URLs per month, respectively.',
},
{
question: 'Can I customize the short URL?',
answer:
'Yes, you can customize your short URL with a custom alias if you are on the Pro or Business plan. This feature is not available on the Free plan.',
},
{
question: 'How secure are my shortened URLs?',
answer:
'We prioritize the security of your data. All shortened URLs are protected with SSL encryption. Additionally, we offer features like password protection and link expiration on our Pro and Business plans.',
},
{
question: 'Can I generate QR code for all my short links?',
answer:
'Yes, you can generate QR codes for all your short links. This feature is available on all our plans. You can download the QR code and use it in your marketing materials.',
},
];
return (
<div class="py-10 px-6 lg:px-8 lg:py-14">
<div class="grid md:grid-cols-8 gap-5">
<div class="md:col-start-3 md:col-span-2 flex justify-center md:justify-start sm:mt-14 mt-0">
<div class="max-w-xs md:w-full">
<h2 class="text-2xl font-bold md:text-4xl md:leading-tight dark:text-white">
Frequently
<br />
asked questions
</h2>
<p class="mt-1 hidden md:block text-gray-600 dark:text-neutral-400">Answers to the most frequently asked questions.</p>
</div>
</div>
<div class="md:col-span-2">
<div class="hs-accordion-group divide-y divide-gray-200 dark:divide-neutral-700">
{faqs.map((faq, index) => (
<div class={`hs-accordion pt-6 pb-3 ${activeAccordion.value === index ? 'active' : ''}`} key={index}>
<button
class="hs-accordion-toggle group pb-3 inline-flex items-center justify-between gap-x-3 w-full md:text-lg font-semibold text-start text-gray-800 rounded-lg transition hover:text-gray-500 dark:text-neutral-200 dark:hover:text-neutral-400"
aria-controls={`hs-accordion-content-${index}`}
onClick$={() => toggleAccordion(index)}
>
{faq.question}
{activeAccordion.value === index ? (
<LuChevronUp class="flex-shrink-0 size-5 text-gray-600 group-hover:text-gray-500 dark:text-neutral-400" />
) : (
<LuChevronDown class="flex-shrink-0 size-5 text-gray-600 group-hover:text-gray-500 dark:text-neutral-400" />
)}
</button>
<div
id={`hs-accordion-content-${index}`}
class={`hs-accordion-content ${
activeAccordion.value === index ? 'block' : 'hidden'
} overflow-hidden text-left transition-[height] duration-300`}
aria-labelledby={`hs-accordion-heading-${index}`}
>
<p class="text-gray-600 dark:text-neutral-400">{faq.answer}</p>
</div>
</div>
))}
</div>
</div>
</div>
</div>
);
});

View File

@ -5,239 +5,251 @@ import { GlobalStore } from '../../context';
export const Features = component$(() => {
const state = useContext(GlobalStore);
return (
<section
id="features"
class="relative bg-gradient-to-b from-white via-slate-300/30 to-white dark:from-slate-900 dark:via-slate-600/30 dark:to-slate-900"
>
<div class="absolute inset-0 -z-10 h-full w-full dark:bg-[#000000] dark:bg-[radial-gradient(#ffffff33_1px,#00091d_1px)] bg-white bg-[radial-gradient(#5c5959,transparent_1px)] bg-[size:20px_20px]"></div>
<div class="relative z-10 py-8 px-4 mx-auto space-y-12 max-w-screen-xl lg:space-y-20 sm:py-16 lg:px-6">
<div class="gap-8 items-center lg:grid lg:grid-cols-2 xl:gap-16">
<div class="text-gray-800 sm:text-lg dark:text-gray-200">
<h1 class="mb-4 text-4xl tracking-tight font-extrabold text-gray-800 dark:text-gray-200">Easily Manage Your Links</h1>
<h2 class="mb-8 font-light text-gray-600 dark:text-gray-400">
Our dashboard provides a simple and intuitive interface for managing all your shortened links. Organize, edit, and track your
links effortlessly with our powerful tools.
</h2>
<ul role="list" class="pt-8 my-7 space-y-5 border-t border-gray-300 dark:border-gray-700">
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-indigo-600 dark:text-indigo-500"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
></path>
</svg>
<span class="text-base font-medium leading-tight text-gray-900 dark:text-white">Comprehensive link management</span>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-indigo-600 dark:text-indigo-500"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
></path>
</svg>
<span class="text-base font-medium leading-tight text-gray-900 dark:text-white">Custom UTM builder</span>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-indigo-600 dark:text-indigo-500"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
></path>
</svg>
<span class="text-base font-medium leading-tight text-gray-900 dark:text-white">Password protected links</span>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-indigo-600 dark:text-indigo-500"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
></path>
</svg>
<span class="text-base font-medium leading-tight text-gray-900 dark:text-white">Branded links</span>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-indigo-600 dark:text-indigo-500"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
></path>
</svg>
<span class="text-base font-medium leading-tight text-gray-900 dark:text-white">User-friendly interface</span>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-indigo-600 dark:text-indigo-500"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
></path>
</svg>
<span class="text-base font-medium leading-tight text-gray-900 dark:text-white">Secure and reliable</span>
</li>
</ul>
</div>
{state.theme === DARK_THEME ? (
<div class="mockup-browser border border-base-300 hidden mb-4 w-full lg:mb-0 lg:block bg-slate-900">
<div class="mockup-browser-toolbar">
<div class="input border border-base-300">https://reduced.to/dashboard</div>
</div>
<img
class="w-full lg:mb-0 lg:flex rounded-lg"
src="/images/features/dashboard-dark.png"
height={1200}
width={1800}
alt="Dashboard feature dark mode preview"
/>
</div>
) : (
<div class="mockup-browser border border-base-300 hidden mb-4 w-full lg:mb-0 lg:block bg-slate-100">
<div class="mockup-browser-toolbar">
<div class="input border border-base-300">https://reduced.to/dashboard</div>
</div>
<img
class="hidden mb-4 w-full lg:mb-0 lg:flex rounded-lg"
src="/images/features/dashboard.png"
height={1200}
width={1800}
alt="Dashboard feature preview"
/>
</div>
)}
<>
<div class="max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14 mx-auto">
<div class="mx-auto text-center mb-10 lg:mb-14 max-w-lg">
<h2 class="font-bold text-gray-800 dark:text-gray-200 text-3xl sm:text-5xl ">
Optimize and Manage Your
<span class="bg-clip-text bg-gradient-to-l from-blue-600 to-violet-600 text-transparent"> Links </span>
with Ease
</h2>
<p class="mt-2 text-gray-600 dark:text-gray-400">
Because your links deserve the best. Explore our powerful features designed for seamless link management and in-depth analytics.
</p>
</div>
<div class="gap-8 items-center lg:grid lg:grid-cols-2 xl:gap-16">
{state.theme === DARK_THEME ? (
<div class="mockup-browser border border-base-300 hidden mb-4 w-full lg:mb-0 lg:block bg-slate-900">
<div class="mockup-browser-toolbar">
<div class="input border border-base-300">https://reduced.to</div>
</div>
<img
class="hidden mb-4 w-full lg:mb-0 lg:flex rounded-lg"
src="/images/features/analytics-dark.png"
height={1200}
width={1800}
alt="Analytics feature dark mode preview"
/>
<div class="relative z-10 py-8 px-4 mx-auto space-y-12 max-w-screen-xl lg:space-y-20 sm:py-16 lg:px-6">
<div class="gap-8 items-center lg:grid lg:grid-cols-2 xl:gap-16">
<div class="text-gray-800 sm:text-lg dark:text-gray-200">
<h1 class="mb-4 sm:text-3xl text-2xl tracking-tight font-extrabold text-gray-800 dark:text-gray-200">
Easily Manage Your Links
</h1>
<h2 class="mb-8 font-light text-gray-600 dark:text-gray-400">
Our dashboard provides a simple and intuitive interface for managing all your shortened links. Organize, edit, and track
your links effortlessly with our powerful tools.
</h2>
<ul role="list" class="pt-8 my-7 space-y-5 border-t border-gray-300 dark:border-gray-700">
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-indigo-600 dark:text-indigo-500"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
></path>
</svg>
<span class="text-base font-medium leading-tight text-gray-900 dark:text-white">Comprehensive link management</span>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-indigo-600 dark:text-indigo-500"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
></path>
</svg>
<span class="text-base font-medium leading-tight text-gray-900 dark:text-white">Custom UTM builder</span>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-indigo-600 dark:text-indigo-500"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
></path>
</svg>
<span class="text-base font-medium leading-tight text-gray-900 dark:text-white">Password protected links</span>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-indigo-600 dark:text-indigo-500"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
></path>
</svg>
<span class="text-base font-medium leading-tight text-gray-900 dark:text-white">Branded links</span>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-indigo-600 dark:text-indigo-500"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
></path>
</svg>
<span class="text-base font-medium leading-tight text-gray-900 dark:text-white">User-friendly interface</span>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-indigo-600 dark:text-indigo-500"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
></path>
</svg>
<span class="text-base font-medium leading-tight text-gray-900 dark:text-white">Secure and reliable</span>
</li>
</ul>
</div>
) : (
<div class="mockup-browser border border-base-300 hidden mb-4 w-full lg:mb-0 lg:block bg-slate-100">
<div class="mockup-browser-toolbar">
<div class="input border border-base-300">https://reduced.to</div>
{state.theme === DARK_THEME ? (
<div class="mockup-browser border border-base-300 hidden mb-4 w-full lg:mb-0 lg:block bg-slate-900">
<div class="mockup-browser-toolbar">
<div class="input border border-base-300">https://reduced.to/dashboard</div>
</div>
<img
class="w-full lg:mb-0 lg:flex rounded-lg"
src="/images/features/dashboard-dark.png"
height={1200}
width={1800}
alt="Dashboard feature dark mode preview"
/>
</div>
<img
class="hidden mb-4 w-full lg:mb-0 lg:flex rounded-lg"
src="/images/features/analytics.png"
height={1200}
width={1800}
alt="Analytics feature preview"
/>
) : (
<div class="mockup-browser border border-base-300 hidden mb-4 w-full lg:mb-0 lg:block bg-slate-100">
<div class="mockup-browser-toolbar">
<div class="input border border-base-300">https://reduced.to/dashboard</div>
</div>
<img
class="hidden mb-4 w-full lg:mb-0 lg:flex rounded-lg"
src="/images/features/dashboard.png"
height={1200}
width={1800}
alt="Dashboard feature preview"
/>
</div>
)}
</div>
<div class="gap-8 items-center lg:grid lg:grid-cols-2 xl:gap-16">
{state.theme === DARK_THEME ? (
<div class="mockup-browser border border-base-300 hidden mb-4 w-full lg:mb-0 lg:block bg-slate-900">
<div class="mockup-browser-toolbar">
<div class="input border border-base-300">https://reduced.to</div>
</div>
<img
class="hidden mb-4 w-full lg:mb-0 lg:flex rounded-lg"
src="/images/features/analytics-dark.png"
height={1200}
width={1800}
alt="Analytics feature dark mode preview"
/>
</div>
) : (
<div class="mockup-browser border border-base-300 hidden mb-4 w-full lg:mb-0 lg:block bg-slate-100">
<div class="mockup-browser-toolbar">
<div class="input border border-base-300">https://reduced.to</div>
</div>
<img
class="hidden mb-4 w-full lg:mb-0 lg:flex rounded-lg"
src="/images/features/analytics.png"
height={1200}
width={1800}
alt="Analytics feature preview"
/>
</div>
)}
<div class="text-gray-800 sm:text-lg dark:text-gray-200">
<h1 class="mb-4 sm:text-3xl text-2xl tracking-tight font-extrabold text-gray-800 dark:text-gray-200">
Advanced analytics for your links
</h1>
<h2 class="mb-8 font-light text-gray-600 dark:text-gray-400">
The analytics dashboard provides detailed insights into your link performance. Track clicks, geographic, and devices data to
optimize your link management strategy.
</h2>
<ul role="list" class="pt-8 my-7 space-y-5 border-t border-gray-300 dark:border-gray-700">
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-indigo-600 dark:text-indigo-500"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
></path>
</svg>
<span class="text-base font-medium leading-tight text-gray-900 dark:text-white">Dynamic reports and dashboards</span>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-indigo-600 dark:text-indigo-500"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
></path>
</svg>
<span class="text-base font-medium leading-tight text-gray-900 dark:text-white">Comprehensive analytics</span>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-indigo-600 dark:text-indigo-500"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
></path>
</svg>
<span class="text-base font-medium leading-tight text-gray-900 dark:text-white">Real-time data</span>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-indigo-600 dark:text-indigo-500"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
></path>
</svg>
<span class="text-base font-medium leading-tight text-gray-900 dark:text-white">Detailed insights</span>
</li>
</ul>
</div>
)}
<div class="text-gray-800 sm:text-lg dark:text-gray-200">
<h1 class="mb-4 text-4xl tracking-tight font-extrabold text-gray-800 dark:text-gray-200">Advanced analytics for your links</h1>
<h2 class="mb-8 font-light text-gray-600 dark:text-gray-400">
The analytics dashboard provides detailed insights into your link performance. Track clicks, geographic, and devices data to
optimize your link management strategy.
</h2>
<ul role="list" class="pt-8 my-7 space-y-5 border-t border-gray-300 dark:border-gray-700">
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-indigo-600 dark:text-indigo-500"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
></path>
</svg>
<span class="text-base font-medium leading-tight text-gray-900 dark:text-white">Dynamic reports and dashboards</span>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-indigo-600 dark:text-indigo-500"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
></path>
</svg>
<span class="text-base font-medium leading-tight text-gray-900 dark:text-white">Comprehensive analytics</span>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-indigo-600 dark:text-indigo-500"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
></path>
</svg>
<span class="text-base font-medium leading-tight text-gray-900 dark:text-white">Real-time data</span>
</li>
<li class="flex space-x-3">
<svg
class="flex-shrink-0 w-5 h-5 text-indigo-600 dark:text-indigo-500"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"
></path>
</svg>
<span class="text-base font-medium leading-tight text-gray-900 dark:text-white">Detailed insights</span>
</li>
</ul>
</div>
</div>
</div>
</section>
</>
);
});

View File

@ -7,15 +7,12 @@ export const Hero = component$(() => {
const user = useGetCurrentUser();
return (
<div
id="hero"
class="relative before:absolute before:top-0 before:start-1/2 before:bg-[url('assets/svg/hero/polygon-bg-element-light.svg')] before:bg-no-repeat before:bg-top before:bg-cover before:w-full before:h-full before:-z-[1] before:transform before:-translate-x-1/2 dark:before:bg-[url('assets/svg/hero/polygon-bg-element-dark.svg')]"
>
<div class="max-w-[85rem] mx-auto px-4 sm:px-6 lg:px-8 pt-24 pb-10">
<div class="pt-24 pb-10">
<div class="max-w-[85rem] mx-auto px-4 sm:px-6 lg:px-8 mt-[-40px]">
{/* <!-- Announcement Github Banner --> */}
<div class="flex justify-center">
<a
class="inline-flex items-center gap-x-2 bg-white border border-gray-200 text-xs text-gray-600 p-2 px-3 rounded-full hover:border-gray-300 dark:bg-slate-800 dark:border-neutral-700 dark:hover:border-neutral-600 dark:text-neutral-400"
class="inline-flex items-center gap-x-2 bg-white z-10 border border-gray-600 text-xs text-gray-600 p-2 px-3 rounded-full hover:border-gray-900 dark:bg-slate-900 dark:border-neutral-700 dark:hover:border-neutral-600 dark:text-slate-200"
href="https://github.com/origranot/reduced.to"
target="_blank"
>
@ -29,7 +26,7 @@ export const Hero = component$(() => {
{/* <!-- Title --> */}
<div class="mt-5 max-w-2xl text-center mx-auto">
<h1 class="block font-bold text-gray-800 text-4xl md:text-5xl lg:text-6xl dark:text-gray-200">
<h1 class="z-10 relative font-bold text-gray-800 text-4xl md:text-5xl lg:text-6xl dark:text-gray-200">
Simplify your
<span class="bg-clip-text bg-gradient-to-l from-blue-600 to-violet-600 text-transparent"> Links</span>
</h1>

View File

@ -11,34 +11,18 @@ export const Navbar = component$(() => {
const showDropdown = useSignal(false);
return (
<div class="navbar bg-base-100 drop-shadow-md fixed z-[40] lg:px-20">
<div class="navbar bg-base-100 drop-shadow-md dark:shadow-slate-700 shadow-sm fixed z-[40] lg:px-20">
<div class="flex-1 flex items-center">
<Link
class="btn btn-ghost normal-case text-xl"
onClick$={(event) => {
event.preventDefault();
const heroSection = document.getElementById('hero');
if (heroSection) {
heroSection.scrollIntoView({ behavior: 'smooth' });
}
}}
>
<a href="/" class="btn btn-ghost normal-case text-xl">
Reduced.to
</Link>
</a>
<div class="hidden sm:flex flex-grow justify-start space-x-4 ml-6">
<Link
title="Features"
class="btn btn-sm btn-ghost"
onClick$={(event) => {
event.preventDefault();
const featuresSection = document.getElementById('features');
if (featuresSection) {
featuresSection.scrollIntoView({ behavior: 'smooth' });
}
}}
>
<a title="Features" href="/features" class="btn btn-sm btn-ghost">
Features
</Link>
</a>
<a title="Pricing" href="/pricing" class="btn btn-sm btn-ghost">
Pricing
</a>
<a href="https://docs.reduced.to" target="_blank" title="Documentation" class="btn btn-sm btn-ghost">
Docs
</a>
@ -59,16 +43,7 @@ export const Navbar = component$(() => {
{showDropdown.value && (
<ul tabIndex={0} class="menu dropdown-content shadow bg-base-100 rounded-box w-52 mt-4 p-2">
<li>
<Link
class="btn-ghost"
onClick$={(event) => {
event.preventDefault();
const featuresSection = document.getElementById('features');
if (featuresSection) {
featuresSection.scrollIntoView({ behavior: 'smooth' });
}
}}
>
<Link href="/features" class="btn-ghost">
Features
</Link>
</li>

View File

@ -0,0 +1,30 @@
import { Signal, component$ } from '@builder.io/qwik';
interface AnnualToggleProps {
annual: Signal<boolean>;
}
export const AnnualToggle = component$(({ annual }: AnnualToggleProps) => {
return (
<div class="flex justify-center items-center">
<label class="min-w-14 text-sm text-gray-500 me-3 dark:text-neutral-400">Monthly</label>
<input type="checkbox" class="toggle toggle-primary" checked={annual.value} onChange$={() => (annual.value = !annual.value)} />
<label class="relative min-w-14 text-sm text-gray-500 ms-3 dark:text-neutral-400">
Yearly
<span class="absolute sm:block hidden -top-10 start-auto -end-28">
<span class="flex items-center">
<svg class="w-14 h-8 -me-6" width="45" height="25" viewBox="0 0 45 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M43.2951 3.47877C43.8357 3.59191 44.3656 3.24541 44.4788 2.70484C44.5919 2.16427 44.2454 1.63433 43.7049 1.52119L43.2951 3.47877ZM4.63031 24.4936C4.90293 24.9739 5.51329 25.1423 5.99361 24.8697L13.8208 20.4272C14.3011 20.1546 14.4695 19.5443 14.1969 19.0639C13.9242 18.5836 13.3139 18.4152 12.8336 18.6879L5.87608 22.6367L1.92723 15.6792C1.65462 15.1989 1.04426 15.0305 0.563943 15.3031C0.0836291 15.5757 -0.0847477 16.1861 0.187863 16.6664L4.63031 24.4936ZM43.7049 1.52119C32.7389 -0.77401 23.9595 0.99522 17.3905 5.28788C10.8356 9.57127 6.58742 16.2977 4.53601 23.7341L6.46399 24.2659C8.41258 17.2023 12.4144 10.9287 18.4845 6.96211C24.5405 3.00476 32.7611 1.27399 43.2951 3.47877L43.7049 1.52119Z"
fill="currentColor"
class="fill-gray-300 dark:fill-neutral-700"
/>
</svg>
<span class="mt-3 inline-block whitespace-nowrap text-[11px] leading-5 font-semibold tracking-wide uppercase bg-blue-600 text-white rounded-full py-1 px-2.5">
2 months free
</span>
</span>
</span>
</label>
</div>
);
});

View File

@ -0,0 +1,64 @@
import { component$ } from '@builder.io/qwik';
import { LuCheck } from '@qwikest/icons/lucide';
import { PLAN_LEVELS, FEATURES, Plan } from '@reduced.to/subscription-manager';
interface BillingCardProps {
isPrefferedPlan: boolean;
billing: 'yearly' | 'monthly';
plan: Plan;
buttonText?: string;
clickHandler?: (plan: Plan, id?: string) => void;
href?: string;
}
export const BillingCard = component$(
({ plan, isPrefferedPlan, billing, href, buttonText = 'Get Started', clickHandler }: BillingCardProps) => {
return (
<div
class={`relative flex flex-col text-center rounded-xl p-8 dark:border-slate-800 bg-white dark:bg-slate-900 ${
isPrefferedPlan ? 'border-2 border-blue-600 shadow-xl dark:border-blue-700' : 'border border-gray-200 dark:border-neutral-700'
}`}
>
{isPrefferedPlan && (
<p class="absolute left-1/2 transform -translate-x-1/2 top-[-10px] right-[-10px] z-20">
<span class="inline-flex items-center gap-1.5 py-1.5 px-3 rounded-lg text-xs uppercase font-semibold bg-blue-100 text-blue-800 dark:bg-blue-600 dark:text-white">
Most popular
</span>
</p>
)}
<h4 class="font-medium text-lg text-gray-800 dark:text-neutral-200">{plan.DISPLAY_NAME}</h4>
<span class="mt-7 font-bold text-5xl text-gray-800 dark:text-neutral-200">
{billing === 'yearly' ? `$${plan.YEARLY_PRICE}` : `$${plan.MONTHLY_PRICE}`}
<span class="text-sm font-normal dark:text-gray-400 text-gray-600"> / month</span>
</span>
<p class="mt-2 text-sm text-gray-500 dark:text-neutral-500">{plan.MONTHLY_PRICE <= 0 ? 'Forever free' : `Billed ${billing}`}</p>
<ul class="mt-7 space-y-2.5 text-sm">
{Object.entries(plan.FEATURES).map(([featureKey, feature]) => {
if (!feature?.marketingText) {
return null;
}
return (
<li class="flex space-x-2" key={featureKey}>
<LuCheck class="h-4 w-4 text-blue-600 dark:text-blue-500 mt-0.5" />
<span class="text-gray-800 dark:text-neutral-400">{feature.marketingText}</span>
</li>
);
})}
</ul>
<a
class="mt-5 py-3 px-4 inline-flex justify-center items-center gap-x-2 text-sm font-semibold rounded-lg border border-gray-200 text-gray-500 hover:border-blue-600 hover:text-blue-600 disabled:opacity-50 disabled:pointer-events-none dark:border-neutral-700 dark:text-neutral-400 dark:hover:text-blue-500 dark:hover:border-blue-600"
href={href || '#'}
onClick$={() => {
clickHandler && clickHandler(plan, billing === 'yearly' ? plan.PADDLE_YEARLY_PRICE_ID : plan.PADDLE_MONTHLY_PRICE_ID);
}}
>
{buttonText}
</a>
{plan.MONTHLY_PRICE <= 0 && <p class="mt-2 text-xs text-gray-500 dark:text-neutral-500">No credit card required</p>}
</div>
);
}
);

View File

@ -0,0 +1,114 @@
import { component$, useSignal } from '@builder.io/qwik';
import { LuCheck } from '@qwikest/icons/lucide';
import { PLAN_LEVELS, FEATURES, FeatureKey } from '@reduced.to/subscription-manager';
import { BillingCard } from './billing-card';
import { AnnualToggle } from './annual-toggle';
import { useGetCurrentUser } from '../../routes/layout';
const PREFERRED_PLAN = PLAN_LEVELS.PRO.DISPLAY_NAME;
const plans = Object.keys(PLAN_LEVELS).map((key) => {
return PLAN_LEVELS[key];
});
export const Pricing = component$(() => {
const annual = useSignal(true);
const user = useGetCurrentUser();
return (
<div class="px-4 py-10 sm:px-6 lg:px-8 lg:py-14">
<div class="mx-auto text-center mb-10 lg:mb-14">
<h2 class="font-bold text-3xl sm:text-5xl md:leading-tight text-gray-800 dark:text-gray-200">Pricing</h2>
<p class="mt-1 text-gray-600 dark:text-gray-400">Whatever your status, our offers evolve according to your needs.</p>
</div>
<div class="mb-12">
{' '}
<AnnualToggle annual={annual} />{' '}
</div>
<div class="flex flex-col md:flex-row justify-center space-y-6 md:space-y-0 md:space-x-6">
{Object.values(plans).map((value, index) => (
<BillingCard
href={`${user.value ? '/dashboard' : '/register'}`}
key={index}
plan={value}
billing={annual.value ? 'yearly' : 'monthly'}
isPrefferedPlan={value.DISPLAY_NAME === PREFERRED_PLAN}
/>
))}
</div>
<div class="mt-12 max-w-[85rem] mx-auto">
<h3 class="text-2xl md:text-4xl font-bold text-center text-gray-800 dark:text-gray-200">Plans Comparison</h3>
<div class="overflow-x-scroll md:overflow-x-visible w-full mt-6 rounded-lg shadow-lg border border-gray-200 dark:border-neutral-700">
<table class="table-fixed overflow-scroll w-full bg-white dark:bg-gray-900">
<thead>
<tr class="divide-x divide-gray-200 dark:divide-neutral-700 border-b border-gray-200 dark:border-neutral-700">
<th class="sticky left-0 z-20 w-40 bg-gray-50 dark:bg-gray-900 p-6 md:top-14 md:w-1/4"></th>
{Object.entries(plans).map(([planKey, plan]) => (
<th class="sticky md:top-14 w-40 bg-gray-50 p-6 dark:bg-gray-900 dark:border-neutral-700" key={planKey}>
<div class="mb-4 flex items-center space-x-2">
<h1 class="font-display text-xl font-bold text-black md:text-2xl dark:text-white">{plan.DISPLAY_NAME}</h1>
</div>
<a
class="block w-full rounded-full py-1 text-center text-sm font-medium text-white transition-all duration-200 ease-in-out hover:ring-[3px] md:py-1.5 md:text-base dark:text-white bg-blue-600/90"
href="/register"
>
{planKey === 'Free' ? 'Start for free' : 'Get started'}
</a>
</th>
))}
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-neutral-700">
{Object.entries(FEATURES).map(([featureKey, feature]) => {
return (
<tr class="divide-x divide-gray-200 dark:divide-neutral-700" key={featureKey}>
<td class="sticky left-0 bg-gray-50 shadow-[5px_0px_10px_-3px_rgba(0,0,0,0.1)] dark:bg-gray-900">
<div class="flex items-center justify-between space-x-2 p-4">
<p class="font-medium text-black dark:text-white">{feature.displayName}</p>
<div class="tooltip tooltip-right z-30" data-tip={feature.tooltip}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-circle-help h-4 w-4 flex-none text-gray-600 dark:text-neutral-400"
>
<circle cx="12" cy="12" r="10"></circle>
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
<path d="M12 17h.01"></path>
</svg>
</div>
</div>
</td>
{Object.entries(plans).map(([planKey, plan]) => (
<td class="p-4 text-center text-gray-600 dark:text-neutral-400 dark:bg-slate-900" key={planKey}>
{plan.FEATURES[featureKey as FeatureKey]?.enabled ? (
!plan.FEATURES[featureKey as FeatureKey].description ? (
<span class="text-center">
<LuCheck class="inline h-4 w-4 text-blue-600 dark:text-blue-500" />
</span>
) : (
plan.FEATURES[featureKey as FeatureKey].description
)
) : (
<span class="text-gray-400 dark:text-neutral-500">-</span>
)}
</td>
))}
</tr>
);
})}
</tbody>
</table>
</div>
</div>
</div>
);
});

View File

@ -44,10 +44,10 @@ const useTempLink = globalAction$(async ({ url }, { fail }) => {
}),
});
const data: { url: string; key: string; message?: string[]; statusCode?: number } = await response.json();
const data: { url: string; key: string; message?: string[] } = await response.json();
if (response.status !== 201) {
return fail(data?.statusCode || 500, {
return fail(500, {
message: data?.message || 'There was an error creating your link. Please try again.',
});
}
@ -137,7 +137,7 @@ export const TemporaryLinks = component$(() => {
<QrCodeDialog link={{ key: interactedLink.value?.key }} />
<div class="mx-auto w-full max-w-md px-2.5 sm:px-0 mb-8">
<div
class={`flex w-full items-center dark:bg-slate-800 rounded-md shadow-lg border border-base-200 p-2 ${
class={`flex w-full items-center dark:bg-slate-800 rounded-md shadow-lg bg-base-100 border border-base-200 p-2 ${
createTempLink.value?.message ? 'border border-red-500' : ''
} ${isInputDisabled.value ? 'bg-gray-200 cursor-not-allowed border-none' : ''}`}
>

View File

@ -1,4 +1,5 @@
const MONTH_NAMES = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
export const getMonthName = (month: number): string => MONTH_NAMES[month];
export const formatDate = (date: Date): string => {
@ -18,3 +19,31 @@ export const tomorrow = (): Date => {
return date;
};
// Function to format the date with the "Renews on March 25th, 2023" format
export const formatRenewalDate = (date: Date): string => {
const options: Intl.DateTimeFormatOptions = {
month: 'long',
day: 'numeric',
year: 'numeric',
};
const dateString = date.toLocaleDateString('en-US', options);
const day = date.getDate();
const daySuffix = (day: number): string => {
if (day > 3 && day < 21) return 'th'; // catch 11th, 12th, 13th
switch (day % 10) {
case 1:
return 'st';
case 2:
return 'nd';
case 3:
return 'rd';
default:
return 'th';
}
};
return dateString.replace(`${day}`, `${day}${daySuffix(day)}`);
};

View File

@ -4,6 +4,9 @@ import { component$, useSignal } from '@builder.io/qwik';
import { ClicksChart } from '../../../../components/dashboard/analytics/clicks-chart/clicks-chart';
import { CountriesChart } from '../../../../components/dashboard/analytics/contries-chart/countries-chart';
import { DevicesChart } from '../../../../components/dashboard/analytics/devices-chart/devices-chart';
import { useGetCurrentUser } from '../../../layout';
import { PLAN_LEVELS } from '@reduced.to/subscription-manager';
import { LuLock } from '@qwikest/icons/lucide';
export const useGetAnalytics = routeLoader$(async ({ params: { key }, cookie, redirect }) => {
const [clicksResponse, countriesResponse, devicesResponse] = await Promise.all([
@ -32,6 +35,35 @@ export const useGetAnalytics = routeLoader$(async ({ params: { key }, cookie, re
export default component$(() => {
const daysDuration = useSignal(7);
const analytics = useGetAnalytics();
const user = useGetCurrentUser();
const plan = PLAN_LEVELS[user.value?.plan || 'FREE'];
const timeframes = [
{
label: 'Last 24 hours',
value: 1,
},
{
label: 'Last 7 days',
value: 7,
},
{
label: 'Last 30 days',
value: 30,
},
{
label: 'Last year',
value: 365,
},
].map((frame) => {
const maxDays = plan.FEATURES.ANALYTICS.value || 1;
if (frame.value <= maxDays) return frame;
return {
...frame,
disabled: true,
};
}) as { label: string; value: number; disabled?: boolean }[];
return (
<>
@ -44,20 +76,21 @@ export default component$(() => {
</p>
</div>
<div class="flex my-auto">
<select
class="select select-bordered max-w-xs"
value={daysDuration.value}
onChange$={(event) => {
daysDuration.value = parseInt((event.target as HTMLSelectElement).value, 10);
}}
>
<option value={1}>Last 24 hours</option>
<option selected value={7}>
Last 7 days
</option>
<option value={30}>Last 30 days</option>
<option value={365}>Last year</option>
</select>
<div class="relative">
<select
class="select select-bordered max-w-xs"
value={daysDuration.value}
onChange$={(event) => {
daysDuration.value = parseInt((event.target as HTMLSelectElement).value, 10);
}}
>
{timeframes.map((frame) => (
<option value={frame.value} disabled={frame.disabled}>
{`${frame.label} ${frame.disabled ? `(Locked)` : ''}`}
</option>
))}
</select>
</div>
</div>
</div>
<ClicksChart

View File

@ -7,8 +7,8 @@ import { SortOrder } from '../../components/dashboard/table/table-server-paginat
import { FilterInput } from '../../components/dashboard/table/default-filter';
import { useToaster } from '../../components/toaster/toaster';
import { NoData } from '../../components/dashboard/empty-data/no-data';
import { DELETE_MODAL_ID, DeleteModal } from '../../components/dashboard/delete-modal/delete-modal';
import { useDeleteLink } from '../../components/dashboard/delete-modal/action';
import { DELETE_MODAL_ID, GenericModal } from '../../components/dashboard/generic-modal/generic-modal';
import { useDeleteLink } from '../../components/dashboard/links/link/use-delete-link';
import { QR_CODE_DIALOG_ID, QrCodeDialog } from '../../components/temporary-links/qr-code-dialog/qr-code-dialog';
import { addUtmParams } from '@reduced.to/utils';
@ -128,11 +128,12 @@ export default component$(() => {
return (
<>
<DeleteModal
<GenericModal
onSubmitHandler={$(() => {
refetch.value++;
})}
idToDelete={idToDelete.value}
operationType="delete"
id={DELETE_MODAL_ID}
confirmation="DELETE"
type="link"

View File

@ -0,0 +1,29 @@
import { globalAction$, z, zod$ } from '@builder.io/qwik-city';
import { serverSideFetch } from '../../../../shared/auth.service';
const CANCEL_CONFIRMATION = 'CANCEL';
export const CANCEL_PLAN_MODAL_ID = 'CANCEL_PLAN_MODAL';
export const useCancelPlan = globalAction$(
async (_, { fail, cookie }) => {
const response = await serverSideFetch(`${process.env.API_DOMAIN}/api/v1/billing/plan`, cookie, {
method: 'DELETE',
});
const data = await response.json();
if (response.status !== 200) {
return fail(500, {
message: data?.message,
});
}
},
zod$({
confirmation: z
.string({
required_error: `Please type ${CANCEL_CONFIRMATION} to confirm.`,
})
.refine((val) => val === CANCEL_CONFIRMATION, {
message: `Please type ${CANCEL_CONFIRMATION} to confirm.`,
}),
})
);

View File

@ -0,0 +1,30 @@
import { globalAction$, z, zod$ } from '@builder.io/qwik-city';
import { serverSideFetch } from '../../../../shared/auth.service';
export const RESUME_CONFIRMATION = 'RESUME';
export const RESUME_PLAN_MODAL_ID = 'RESUME_PLAN_MODAL';
export const useResumePlan = globalAction$(
async (_, { fail, cookie }) => {
const response = await serverSideFetch(`${process.env.API_DOMAIN}/api/v1/billing/plan/resume`, cookie, {
method: 'PATCH',
});
const data = await response.json();
if (response.status !== 200) {
return fail(500, {
message: data?.message,
});
}
},
zod$({
confirmation: z
.string({
required_error: `Please type ${RESUME_CONFIRMATION} to confirm.`,
})
.refine((val) => val === RESUME_CONFIRMATION, {
message: `Please type ${RESUME_CONFIRMATION} to confirm.`,
}),
})
);

View File

@ -0,0 +1,26 @@
import { globalAction$ } from '@builder.io/qwik-city';
import { sleep } from '@reduced.to/utils';
import { REFRESH_COOKIE_NAME, refreshTokens, setTokensAsCookies } from '../../../../shared/auth.service';
export const useRevalidatePlan = globalAction$(async (_, { fail, cookie }) => {
for (let i = 0; i < 4; i++) {
try {
const refreshToken = cookie.get(REFRESH_COOKIE_NAME)?.value;
if (!refreshToken) {
console.error('No refresh token found in cookies');
return fail(401, { message: 'Unauthorized' });
}
const { accessToken, refreshToken: newRefresh } = await refreshTokens(refreshToken);
setTokensAsCookies(accessToken, newRefresh, cookie);
console.debug('Tokens refreshed successfully');
} catch (error) {
// Do nothing
}
await sleep(200); // Wait before retrying
}
return fail(500, { message: 'Failed to refresh tokens after multiple attempts' });
});

View File

@ -1,10 +1,15 @@
import { component$, $, useSignal } from '@builder.io/qwik';
import { DocumentHead, Form, Link, globalAction$, z, zod$ } from '@builder.io/qwik-city';
import { component$, $, useSignal, Resource, useVisibleTask$, NoSerialize, noSerialize } from '@builder.io/qwik';
import { DocumentHead, Form, Link, globalAction$, z, zod$, routeLoader$ } from '@builder.io/qwik-city';
import { useGetCurrentUser } from '../../layout';
import { useToaster } from '../../../components/toaster/toaster';
import { ACCESS_COOKIE_NAME, setTokensAsCookies } from '../../../shared/auth.service';
import { ACCESS_COOKIE_NAME, serverSideFetch, setTokensAsCookies } from '../../../shared/auth.service';
import { resizeImage } from '../../../utils/images';
import { DELETE_CONFIRMATION, DELETE_MODAL_ID, DeleteModal } from '../../../components/dashboard/delete-modal/delete-modal';
import { DELETE_CONFIRMATION, DELETE_MODAL_ID, GenericModal } from '../../../components/dashboard/generic-modal/generic-modal';
import { capitalizeFirstLetter } from '../../../utils/strings';
import { formatRenewalDate } from '../../../lib/date-utils';
import * as Paddle from '@paddle/paddle-js';
import { RESUME_CONFIRMATION, RESUME_PLAN_MODAL_ID, useResumePlan } from './billing/use-resume-plan';
import { PLAN_MODAL_ID, PlanModal } from '../../../components/dashboard/plan-modal/plan-modal';
const MAX_FILE_SIZE = 1024 * 1024; // 1MB
const ACCEPTED_IMAGE_TYPES = ['image/jpeg', 'image/jpg', 'image/png'];
@ -22,7 +27,7 @@ const useDeleteUser = globalAction$(
const data = await response.json();
if (response.status !== 200) {
return fail(data?.statusCode || 500, {
return fail(500, {
message: data?.message,
});
}
@ -130,6 +135,16 @@ export const updateProfile = globalAction$(
})
);
export const useBillingInfo = routeLoader$(async ({ cookie }) => {
const response = await serverSideFetch(`${process.env.API_DOMAIN}/api/v1/billing/info`, cookie);
if (!response.ok) {
throw new Error('Failed to fetch billing info');
}
return response.json();
});
export default component$(() => {
const updateProfileAction = updateProfile();
const user = useGetCurrentUser();
@ -137,6 +152,23 @@ export default component$(() => {
const profilePicture = useSignal(user.value?.profilePicture);
const displayName = useSignal(user.value?.name);
const deleteAction = useDeleteUser();
const resumePlanAction = useResumePlan();
const billingInfo = useBillingInfo();
const paddle = useSignal<NoSerialize<Paddle.Paddle | undefined>>(undefined);
useVisibleTask$(async () => {
const publicKey = import.meta.env.PUBLIC_PADDLE_KEY;
if (!publicKey) {
return;
}
paddle.value = noSerialize(
await Paddle.initializePaddle({
token: publicKey,
environment: import.meta.env.DEV ? 'sandbox' : 'production',
})
);
});
const onUploadProfilePicture = $(async (event: Event) => {
const file = (event.target as HTMLInputElement).files![0];
@ -180,7 +212,15 @@ export default component$(() => {
return (
<>
<DeleteModal id={DELETE_MODAL_ID} confirmation="DELETE" type="account" action={deleteAction} />
<PlanModal paddle={paddle.value} id={PLAN_MODAL_ID} />
<GenericModal id={DELETE_MODAL_ID} confirmation="DELETE" operationType="delete" type="account" action={deleteAction} />
<GenericModal
id={RESUME_PLAN_MODAL_ID}
confirmation={RESUME_CONFIRMATION}
operationType="resume"
type="subscription"
action={resumePlanAction}
/>
<div class="shadow-[0_8px_30px_rgb(0,0,0,0.12)] rounded-xl w-full p-5 text-left">
<Form
action={updateProfileAction}
@ -289,6 +329,106 @@ export default component$(() => {
</div>
</Form>
<div class="divider py-3"></div>
<div class="block sm:grid grid-cols-3 gap-4">
<div>
<div class="font-bold">Billing</div>
<span class="text-sm text-gray-500">Manage your billing information</span>
</div>
<div class="pt-4 sm:pt-0 col-span-2 w-full md:w-2/3 sm:w-full">
<Resource
value={billingInfo}
onPending={() => <span>Loading...</span>}
onResolved={(billingInfo) => {
const { usage, limits, scheduledToBeCancelled } = billingInfo;
const linkUsagePercentage = (usage.currentLinkCount / limits.linksCount) * 100;
const clicksUsagePercentage = (usage.currentTrackedClicks / limits.trackedClicks) * 100;
const getProgressBarClass = (percentage: number) => {
if (percentage >= 90) return 'bg-red-500';
if (percentage >= 70) return 'bg-yellow-500';
return 'bg-blue-500';
};
const pingColor = scheduledToBeCancelled ? 'bg-yellow-500' : 'bg-green-500';
return (
<div>
<div class="flex justify-between items-center">
<div class="flex items-center">
<h2 class="text-lg font-bold mr-2">{capitalizeFirstLetter(billingInfo.plan)}</h2>
<div class="relative flex items-center">
<div class={`absolute inline-flex h-2 w-2 rounded-full ${pingColor}`}></div>
<div class={`absolute inline-flex h-2 w-2 rounded-full ${pingColor} opacity-75 animate-ping`}></div>
</div>
</div>
<div class="flex w-full justify-end gap-x-4">
{scheduledToBeCancelled ? (
<button
class="btn btn-sm btn-primary"
onClick$={$(() => {
(document.getElementById(RESUME_PLAN_MODAL_ID) as any).showModal();
})}
>
Resume subscription
</button>
) : (
<button
class="btn btn-sm btn-primary"
onClick$={$(() => {
(document.getElementById(PLAN_MODAL_ID) as any).showModal();
})}
>
{billingInfo.plan === 'FREE' ? 'Upgrade plan' : 'Change plan'}
</button>
)}
</div>
</div>
{billingInfo.endDate || billingInfo.nextBillingAt ? (
<div class="mb-4 text-gray-500">
<span class="text-sm">
{scheduledToBeCancelled ? 'Scheduled to be cancelled' : 'Renews on'} on{' '}
{formatRenewalDate(new Date(billingInfo.nextBillingAt))}
</span>
</div>
) : (
<div class="mb-4"></div>
)}
<div class="mb-4">
<div class="flex justify-between">
<span class="block text-sm font-semibold mb-1">Links Created</span>
<span class="text-sm font-bold">
{billingInfo.usage.currentLinkCount} / {billingInfo.limits.linksCount}{' '}
<span class="text-xs font-normal text-gray-400">({linkUsagePercentage}%)</span>
</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2.5 mb-2">
<div
class={`h-2.5 rounded-full ${getProgressBarClass(linkUsagePercentage)}`}
style={{ width: `${linkUsagePercentage > 100 ? 100 : linkUsagePercentage}%` }}
></div>
</div>
</div>
<div class="mb-4">
<div class="flex justify-between">
<span class="block text-sm font-semibold mb-1">Tracked Clicks</span>
<span class="text-sm font-bold">
{billingInfo.usage.currentTrackedClicks} / {billingInfo.limits.trackedClicks}{' '}
<span class="text-xs font-normal text-gray-400">({clicksUsagePercentage}%)</span>
</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2.5 mb-2">
<div
class={`h-2.5 rounded-full ${getProgressBarClass(clicksUsagePercentage)}`}
style={{ width: `${clicksUsagePercentage > 100 ? 100 : clicksUsagePercentage}%` }}
></div>
</div>
</div>
</div>
);
}}
/>
</div>
</div>
<div class="divider py-3"></div>
<div class="block sm:grid grid-cols-3 gap-4">
<div>
<div class="font-bold">Delete account</div>

View File

@ -0,0 +1,71 @@
import { component$ } from '@builder.io/qwik';
import { Footer } from '../../components/footer/footer';
import { DocumentHead } from '@builder.io/qwik-city';
import { Features } from '../../components/features/features';
import { Background } from '../../components/background/background';
export default component$(() => {
return (
<>
<Background />
<Features />
<Footer />
</>
);
});
export const head: DocumentHead = {
title: 'Features Page | Reduced.to - Free & Open-Source URL Shortener',
meta: [
{
name: 'title',
content: 'Features Page | Reduced.to - Free & Open-Source URL Shortener',
},
{
name: 'description',
content:
'Discover the features of Reduced.to, the free and open-source URL shortener. Learn how to simplify your links and enhance your link sharing and tracking!',
},
{
property: 'og:type',
content: 'website',
},
{
property: 'og:url',
content: 'https://reduced.to',
},
{
property: 'og:title',
content: 'Features Page | Reduced.to - Free & Open-Source URL Shortener',
},
{
property: 'og:description',
content:
'Discover the features of Reduced.to, the free and open-source URL shortener. Learn how to simplify your links and enhance your link sharing and tracking!',
},
{
property: 'og:image',
content: 'https://reduced.to/images/thumbnail.png',
},
{
property: 'twitter:card',
content: 'summary_large_image',
},
{
property: 'twitter:url',
content: 'https://reduced.to',
},
{
property: 'twitter:title',
content: 'Features Page | Reduced.to - Free & Open-Source URL Shortener',
},
{
property: 'twitter:description',
content:
'Discover the features of Reduced.to, the free and open-source URL shortener. Learn how to simplify your links and enhance your link sharing and tracking!',
},
{
property: 'twitter:image',
content: 'https://reduced.to/images/thumbnail.png',
},
],
};

View File

@ -5,7 +5,7 @@ import styles from './index.css?inline';
import { Hero } from '../components/hero/hero';
import { Footer } from '../components/footer/footer';
import { TemporaryLinks } from '../components/temporary-links/temporary-links';
import { Features } from '../components/features/features';
import { Background } from '../components/background/background';
export default component$(() => {
useStylesScoped$(animations);
@ -13,9 +13,9 @@ export default component$(() => {
return (
<>
<Background />
<Hero />
<TemporaryLinks />
<Features />
<Footer />
</>
);

View File

@ -1,5 +1,5 @@
import { component$, Slot } from '@builder.io/qwik';
import { routeLoader$, useLocation } from '@builder.io/qwik-city';
import { routeAction$, routeLoader$, useLocation } from '@builder.io/qwik-city';
import jwt_decode from 'jwt-decode';
import { ACCESS_COOKIE_NAME, refreshTokens, REFRESH_COOKIE_NAME, setTokensAsCookies } from '../shared/auth.service';
import { VerifyAlert } from '../components/verify-alert/verify-alert';
@ -8,6 +8,7 @@ import { Toaster, useToasterProvider } from '../components/toaster/toaster';
import { getProfilePictureUrl } from '../components/dashboard/navbar/profile/profile';
import { DashboardNavbar } from '../components/dashboard/navbar/navbar';
import { Navbar } from '../components/navbar/navbar';
import { retry } from 'rxjs';
export enum Role {
ADMIN = 'ADMIN',
@ -18,6 +19,7 @@ export interface UserCtx {
name: string;
email: string;
role: Role;
plan: string;
verified: boolean;
profilePicture: string;
}

View File

@ -0,0 +1,73 @@
import { component$ } from '@builder.io/qwik';
import { Footer } from '../../components/footer/footer';
import { DocumentHead } from '@builder.io/qwik-city';
import { Pricing } from '../../components/pricing/pricing';
import { Background } from '../../components/background/background';
import { Faq } from '../../components/faq/faq';
export default component$(() => {
return (
<>
<Background />
<Pricing />
<Faq />
<Footer />
</>
);
});
export const head: DocumentHead = {
title: 'Pricing & Plans | Reduced.to - Free & Open-Source URL Shortener',
meta: [
{
name: 'title',
content: 'Pricing & Plans | Reduced.to - Free & Open-Source URL Shortener',
},
{
name: 'description',
content:
'Explore the pricing and plans of Reduced.to, the free and open-source URL shortener. Choose the best plan for your needs and start simplifying your links today!',
},
{
property: 'og:type',
content: 'website',
},
{
property: 'og:url',
content: 'https://reduced.to',
},
{
property: 'og:title',
content: 'Pricing & Plans | Reduced.to - Free & Open-Source URL Shortener',
},
{
property: 'og:description',
content:
'Explore the pricing and plans of Reduced.to, the free and open-source URL shortener. Choose the best plan for your needs and start simplifying your links today!',
},
{
property: 'og:image',
content: 'https://reduced.to/images/thumbnail.png',
},
{
property: 'twitter:card',
content: 'summary_large_image',
},
{
property: 'twitter:url',
content: 'https://reduced.to',
},
{
property: 'twitter:title',
content: 'Pricing & Plans | Reduced.to - Free & Open-Source URL Shortener',
},
{
property: 'twitter:description',
content:
'Explore the pricing and plans of Reduced.to, the free and open-source URL shortener. Choose the best plan for your needs and start simplifying your links today!',
},
{
property: 'twitter:image',
content: 'https://reduced.to/images/thumbnail.png',
},
],
};

View File

@ -1,5 +1,5 @@
import { component$, useStore } from '@builder.io/qwik';
import { DocumentHead, Form, Link, RequestHandler, globalAction$, z, zod$ } from '@builder.io/qwik-city';
import { component$, useStore, useVisibleTask$ } from '@builder.io/qwik';
import { DocumentHead, Form, Link, RequestHandler, globalAction$, useLocation, z, zod$ } from '@builder.io/qwik-city';
import { setTokensAsCookies, validateAccessToken } from '../../shared/auth.service';
interface RegisterStore {
@ -40,6 +40,7 @@ export const useRegister = globalAction$(
setTokensAsCookies(accessToken, refreshToken, cookie);
// Redirect using location header instead of redirect becuase we need to reload the routeLoader to get the new user data
headers.set('location', '/register/verify');
return {

View File

@ -105,7 +105,7 @@ export default component$(() => {
<p class="mt-2 mb-8">Your account is verified</p>
<div class="form-control w-full max-w-xs inline-flex">
<br />
<Link href="/dashboard" class="btn btn-primary">
<Link href={'/dashboard'} class="btn btn-primary">
Go back
</Link>
</div>

View File

@ -70,6 +70,7 @@ export const serverSideFetch = async (url: string, cookies: Cookie, options = {}
return authorizedFetch(url, {
headers: {
contentType: 'application/json',
Authorization: `Bearer ${accessToken}`,
},
...options,
@ -81,6 +82,7 @@ export const authorizedFetch = async (url: string, options = {}) => {
if (response.status === 401) {
// Attempt to refresh the token
console.debug('Attempt to refresh the token');
const refreshResponse = await fetch(`${process.env.API_DOMAIN}/api/v1/auth/refresh`, {
method: 'POST',
credentials: 'include',
@ -110,6 +112,7 @@ export const refreshTokens = async (refreshToken: string): Promise<{ accessToken
if (res.ok) {
const { accessToken, refreshToken } = await res.json();
return {
accessToken,
refreshToken,

View File

@ -0,0 +1,4 @@
export function capitalizeFirstLetter(str: string): string {
if (!str) return '';
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}

View File

@ -14,6 +14,7 @@
"skipLibCheck": true,
"incremental": true,
"isolatedModules": true,
"experimentalDecorators": true,
"outDir": "tmp",
"noEmit": true,
"types": ["node", "vite/client", "vitest"]

View File

@ -3,11 +3,16 @@ import { qwikCity } from '@builder.io/qwik-city/vite';
import { defineConfig, loadEnv } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
import { qwikNxVite } from 'qwik-nx/plugins';
import path from 'path';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
return {
resolve: {
alias: {
'@reduced.to/subscription-manager': path.resolve(__dirname, '../../libs/subscription-manager/src/static'),
},
},
cacheDir: '../../node_modules/.vite/apps/frontend',
plugins: [
qwikNxVite(),
@ -23,6 +28,7 @@ export default defineConfig(({ mode }) => {
tsconfigPaths({ root: '../../' }),
],
define: {
'process.env.PUBLIC_PADDLE_KEY': JSON.stringify(env.PUBLIC_PADDLE_KEY),
'process.env.DOMAIN': JSON.stringify(env.DOMAIN),
'process.env.API_DOMAIN': JSON.stringify(env.API_DOMAIN),
'process.env.CLIENTSIDE_API_DOMAIN': JSON.stringify(env.CLIENTSIDE_API_DOMAIN),

View File

@ -7,10 +7,18 @@ import { KafkaMessage } from 'kafkajs';
import { isbot } from 'isbot';
import geoip from 'geoip-lite';
import { VisitsService } from './visits.service';
import { PrismaService } from '@reduced.to/prisma';
import { UsageService } from '@reduced.to/subscription-manager';
@Injectable()
export class VisitsConsumer extends ConsumerService {
constructor(config: AppConfigService, private readonly loggerService: AppLoggerService, private readonly visitsService: VisitsService) {
constructor(
config: AppConfigService,
private readonly loggerService: AppLoggerService,
private readonly prismaService: PrismaService,
private readonly visitsService: VisitsService,
private readonly usageService: UsageService
) {
super(config.getConfig().tracker.stats.topic);
}
@ -24,6 +32,25 @@ export class VisitsConsumer extends ConsumerService {
this.loggerService.debug(`Received message for ${key} with ip: ${ip} and user agent: ${userAgent}`);
const user = await this.prismaService.link.findUnique({
where: { key },
select: {
userId: true,
},
});
if (!user) {
this.loggerService.debug('Could not find user id for key: ', key);
return;
}
const isEligleToTrackClick = await this.usageService.isEligibleToTrackClicks(user.userId);
if (!isEligleToTrackClick) {
this.loggerService.debug('User has reached the limit of tracked clicks');
return;
}
const hashedIp = createHash('sha256').update(ip).digest('hex');
const isUniqueVisit = await this.visitsService.isUnique(key, hashedIp);
@ -39,11 +66,14 @@ export class VisitsConsumer extends ConsumerService {
const geoLocation = geoip.lookup(ip);
this.loggerService.debug(`Parsed ip ${ip} to geo location: ${JSON.stringify(geoLocation)}`);
await this.visitsService.add(key, {
hashedIp,
ua: userAgent,
geoLocation,
});
await Promise.all([
this.visitsService.add(key, {
hashedIp,
ua: userAgent,
geoLocation,
}),
this.usageService.incrementClicksCount(user.userId),
]);
this.loggerService.log(`Added unique visit for ${key}`);
}

View File

@ -3,9 +3,10 @@ import { VisitsConsumer } from './visits.consumer';
import { QueueManagerModule, QueueManagerService } from '@reduced.to/queue-manager';
import { PrismaModule } from '@reduced.to/prisma';
import { VisitsService } from './visits.service';
import { UsageModule } from '@reduced.to/subscription-manager';
@Module({
imports: [PrismaModule, QueueManagerModule],
imports: [PrismaModule, QueueManagerModule, UsageModule],
providers: [VisitsService, VisitsConsumer, QueueManagerService],
exports: [VisitsService],
})

View File

@ -12,13 +12,6 @@ export class VisitsService {
const { hashedIp, ua, geoLocation } = opts;
return this.prismaService.$transaction(async (prisma) => {
const linkExists = await prisma.link.findUnique({
where: { key },
select: { id: true },
});
if (!linkExists) return; // Early return if link does not exist
const { browser, os, device } = await this.parseUa(ua);
await prisma.visit.create({

View File

@ -13,11 +13,17 @@ data:
RATE_LIMIT_TTL: '60'
RATE_LIMIT_COUNT: '100'
# PADDLE - (Payment Gateway)
PADDLE_ENABLE: 'false'
PADDLE_WEBHOOK_KEY: 'pld_test_webhook_key'
PADDLE_SECRET_KEY: 'secret_key'
# LOGGER
LOGGER_CONSOLE_THRESHOLD: 'INFO' # DEBUG, INFO, WARN, ERROR, FATAL
# FRONTEND
DOMAIN: 'reduced.to'
PUBLIC_PADDLE_KEY: 'test_public_key'
CLIENTSIDE_API_DOMAIN: 'https://reduced.to' # Use this variable while making client-side API calls
API_DOMAIN: 'http://backend-service:3000' # Use this variable while making server-side API calls
STORAGE_DOMAIN: 'Get it from https://cloud.digitalocean.com/spaces'

View File

@ -15,6 +15,11 @@ export const configFactory: ConfigFactory<{ config: Configuration }> = () => {
threshold: LOG_LEVEL[process.env.LOGGER_CONSOLE_THRESHOLD] || LOG_LEVEL.INFO,
},
},
paddle: {
enable: process.env.PADDLE_ENABLE === 'true' || false,
secret: process.env.PADDLE_SECRET_KEY,
webhookSecret: process.env.PADDLE_WEBHOOK_KEY,
},
front: {
domain: process.env.DOMAIN || 'localhost',
clientSideApiDomain: process.env.CLIENTSIDE_API_DOMAIN || 'http://localhost:3000',
@ -24,6 +29,7 @@ export const configFactory: ConfigFactory<{ config: Configuration }> = () => {
ttl: +process.env.RATE_LIMIT_TTL || 60,
limit: +process.env.RATE_LIMIT_COUNT || 10,
},
redis: {
enable: process.env.REDIS_ENABLE === 'true' || false,
host: process.env.REDIS_HOST || 'localhost',
@ -147,6 +153,12 @@ export interface StorageConfig {
bucket: string;
}
export interface PaddleConfig {
enable?: boolean;
secret: string;
webhookSecret: string;
}
export interface Configuration {
general: GeneralConfig;
logger: LoggerConfig;
@ -159,4 +171,5 @@ export interface Configuration {
safeUrl: SafeUrlConfig;
tracker: TrackerConfig;
storage: StorageConfig;
paddle: PaddleConfig;
}

View File

@ -1,5 +1,7 @@
import { z } from 'zod';
export default z.object({
PADDLE_SECRET_KEY: z.string(),
// General
BACKEND_APP_PORT: z.string(),
FRONTEND_APP_PORT: z.string(),

View File

@ -0,0 +1,92 @@
-- CreateEnum
CREATE TYPE "Plan" AS ENUM ('FREE', 'PRO', 'BUSINESS');
-- CreateTable
CREATE TABLE "Subscription" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"plan" "Plan" NOT NULL,
"status" TEXT NOT NULL,
"startDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"scheduledToBeCancelled" BOOLEAN NOT NULL DEFAULT false,
"endDate" TIMESTAMP(3),
"nextBilledAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Subscription_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Usage" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"linksCount" INTEGER NOT NULL DEFAULT 0,
"linksLimit" INTEGER NOT NULL DEFAULT 5,
"clicksCount" INTEGER NOT NULL DEFAULT 0,
"clicksLimit" INTEGER NOT NULL DEFAULT 250,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Usage_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Subscription_userId_key" ON "Subscription"("userId");
-- CreateIndex
CREATE INDEX "Subscription_userId_idx" ON "Subscription"("userId");
-- CreateIndex
CREATE INDEX "Subscription_plan_idx" ON "Subscription"("plan");
-- CreateIndex
CREATE INDEX "Subscription_status_idx" ON "Subscription"("status");
-- CreateIndex
CREATE INDEX "Subscription_startDate_idx" ON "Subscription"("startDate");
-- CreateIndex
CREATE INDEX "Subscription_endDate_idx" ON "Subscription"("endDate");
-- CreateIndex
CREATE UNIQUE INDEX "Usage_userId_key" ON "Usage"("userId");
-- CreateIndex
CREATE INDEX "Usage_userId_idx" ON "Usage"("userId");
-- CreateIndex
CREATE INDEX "Link_createdAt_idx" ON "Link"("createdAt");
-- CreateIndex
CREATE INDEX "Link_expirationTime_idx" ON "Link"("expirationTime");
-- CreateIndex
CREATE INDEX "Report_createdAt_idx" ON "Report"("createdAt");
-- CreateIndex
CREATE INDEX "User_email_idx" ON "User"("email");
-- CreateIndex
CREATE INDEX "User_verificationToken_idx" ON "User"("verificationToken");
-- CreateIndex
CREATE INDEX "Visit_ip_idx" ON "Visit"("ip");
-- CreateIndex
CREATE INDEX "Visit_country_idx" ON "Visit"("country");
-- CreateIndex
CREATE INDEX "Visit_region_idx" ON "Visit"("region");
-- CreateIndex
CREATE INDEX "Visit_city_idx" ON "Visit"("city");
-- CreateIndex
CREATE INDEX "Visit_createdAt_idx" ON "Visit"("createdAt");
-- AddForeignKey
ALTER TABLE "Subscription" ADD CONSTRAINT "Subscription_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Usage" ADD CONSTRAINT "Usage_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -18,12 +18,57 @@ model User {
email String @unique
password String?
role Role @default(USER)
verified Boolean @default(false) // Is the mail verification
verified Boolean @default(false)
verificationToken String?
refreshToken String?
links Link[]
createdAt DateTime @default(now())
authProviders AuthProvider[]
subscription Subscription?
usage Usage?
@@index(email)
@@index(verificationToken)
}
model Subscription {
id String @id @default(uuid()) // Same id as in Paddle
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String @unique
plan Plan
status String // e.g., 'active', 'cancelled'
startDate DateTime @default(now())
scheduledToBeCancelled Boolean @default(false)
endDate DateTime?
nextBilledAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index(userId)
@@index(plan)
@@index(status)
@@index(startDate)
@@index(endDate)
}
model Usage {
id String @id @default(uuid())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String @unique
linksCount Int @default(0)
linksLimit Int @default(5)
clicksCount Int @default(0)
clicksLimit Int @default(250)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index(userId)
}
enum Plan {
FREE
PRO
BUSINESS
}
enum ProviderType {
@ -58,6 +103,8 @@ model Link {
@@index(userId)
@@index(key)
@@index([url, clicks])
@@index(createdAt)
@@index(expirationTime)
}
model Report {
@ -69,6 +116,7 @@ model Report {
@@index(linkId)
@@index(category)
@@index(createdAt)
}
model Visit {
@ -88,6 +136,11 @@ model Visit {
createdAt DateTime @default(now())
@@index(linkId)
@@index(ip)
@@index(country)
@@index(region)
@@index(city)
@@index(createdAt)
}
enum Role {

View File

@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@ -0,0 +1,7 @@
# subscription-manager
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test subscription-manager` to execute the unit tests via [Jest](https://jestjs.io).

View File

@ -0,0 +1,11 @@
/* eslint-disable */
export default {
displayName: 'subscription-manager',
preset: '../../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/libs/subscription-manager',
};

View File

@ -0,0 +1,30 @@
{
"name": "subscription-manager",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/subscription-manager/src",
"projectType": "library",
"targets": {
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/subscription-manager/**/*.ts"]
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/subscription-manager/jest.config.ts",
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
}
},
"tags": []
}

View File

@ -0,0 +1,2 @@
export * from './lib/limits';
export * from './lib/usage';

View File

@ -0,0 +1,178 @@
interface Limit {
marketingText?: string;
enabled: boolean;
description?: string;
value?: number;
}
export const FEATURES = {
LINKS_COUNT: {
tooltip: 'Number of new links you can create each month',
displayName: 'Short Links',
},
TRACKED_CLICKS: {
tooltip: 'Number of clicks that will be tracked in the dashboard',
displayName: 'Tracked Clicks',
},
ANALYTICS: {
tooltip: 'The number of days you can see analytics for your links',
displayName: 'Analytics reports',
},
CLICK_COUNT: {
tooltip: 'Number of redirects on your links, it will be unlimited for all plans',
displayName: 'Link Clicks',
},
UTM_BUILDER: {
displayName: 'UTM Builder',
tooltip: 'Add UTM parameters to your links',
apiGuard: /^utm_.*$/,
},
QR_CODES: {
displayName: 'QR Code',
tooltip: 'Generate QR codes for your links',
},
PASSWORD_PROTECTION: {
displayName: 'Password Protection',
tooltip: 'Protect your links with a password',
apiGuard: 'password',
},
LINK_EXPIRATION: {
displayName: 'Link Expiration',
tooltip: 'Set an expiration date for your links',
apiGuard: 'expirationTime',
},
CUSTOM_SHORT_KEY: {
displayName: 'Custom Short Key',
tooltip: 'Create custom short keys for your links',
apiGuard: 'key',
},
};
export type FeatureKey = keyof typeof FEATURES;
export interface Plan {
DISPLAY_NAME: string;
LEVEL: number;
FEATURES: {
[key in FeatureKey]: Limit;
};
MONTHLY_PRICE: number;
YEARLY_PRICE: number;
PADDLE_PLAN_ID?: string;
PADDLE_YEARLY_PRICE_ID?: string;
PADDLE_MONTHLY_PRICE_ID?: string;
}
const BASE_PLAN: Plan = {
DISPLAY_NAME: 'Free',
LEVEL: 1,
FEATURES: {
LINKS_COUNT: {
marketingText: '5 new links/mo',
description: '5/month',
value: 5,
enabled: true,
},
TRACKED_CLICKS: {
marketingText: '250 tracked clicks/mo',
description: '250/month',
value: 250,
enabled: true,
},
ANALYTICS: {
description: '7 days analytics',
marketingText: '7 days analytics',
value: 7,
enabled: true,
},
CLICK_COUNT: {
marketingText: 'Unlimited link clicks',
description: 'Unlimited',
value: 1,
enabled: true,
},
LINK_EXPIRATION: {
enabled: false,
},
UTM_BUILDER: { enabled: true },
PASSWORD_PROTECTION: { enabled: false },
QR_CODES: { enabled: false },
CUSTOM_SHORT_KEY: { enabled: false },
},
MONTHLY_PRICE: 0,
YEARLY_PRICE: 0,
};
const PRO_PLAN: Plan = {
DISPLAY_NAME: 'Pro',
LEVEL: 2,
FEATURES: {
...BASE_PLAN.FEATURES,
LINKS_COUNT: {
marketingText: '500 new links/mo',
enabled: true,
description: '500/month',
value: 500,
},
TRACKED_CLICKS: {
marketingText: '20,000 tracked clicks/mo',
enabled: true,
description: '20,000/month',
value: 20000,
},
ANALYTICS: {
marketingText: '30 days analytics',
enabled: true,
description: '30 days analytics',
value: 30,
},
LINK_EXPIRATION: { enabled: true },
PASSWORD_PROTECTION: { enabled: true },
UTM_BUILDER: { enabled: true },
QR_CODES: { enabled: true },
CUSTOM_SHORT_KEY: { enabled: true },
},
MONTHLY_PRICE: 9,
YEARLY_PRICE: 90,
PADDLE_PLAN_ID: 'pro_01hz1n5gnw3mgtv7whqszxm13b',
PADDLE_YEARLY_PRICE_ID: 'pri_01hz1nbnrf8fadhh0k3qr2y8c7',
PADDLE_MONTHLY_PRICE_ID: 'pri_01hz1n789t5sghzzn2vjw3qgr3',
};
const BUSINESS_PLAN: Plan = {
DISPLAY_NAME: 'Business',
LEVEL: 3,
FEATURES: {
...PRO_PLAN.FEATURES,
LINKS_COUNT: {
marketingText: '2000 new links/mo',
enabled: true,
description: '2000/month',
value: 2000,
},
TRACKED_CLICKS: {
marketingText: '100,000 tracked clicks/mo',
enabled: true,
description: '100,000/month',
value: 100000,
},
ANALYTICS: {
marketingText: '1 year analytics',
enabled: true,
description: '1 year analytics',
value: 365,
},
},
MONTHLY_PRICE: 30,
YEARLY_PRICE: 300,
PADDLE_PLAN_ID: 'pro_01hz1n9qjsn64g8kk68ex60sq6',
PADDLE_YEARLY_PRICE_ID: 'pri_01hz1nc8qy3pwn5tx2d5bfc7ab',
PADDLE_MONTHLY_PRICE_ID: 'pri_01hz1naabytns9mpr57zz4c7zh',
};
export const PLAN_LEVELS: Record<string, Plan> = {
FREE: BASE_PLAN,
PRO: PRO_PLAN,
BUSINESS: BUSINESS_PLAN,
};
export function getPlanByPaddleId(id: string): Plan | undefined {
return Object.values(PLAN_LEVELS).find((plan) => plan.PADDLE_PLAN_ID == id);
}

View File

@ -0,0 +1,6 @@
export interface Subscription {
level: number;
status: string;
startDate: Date;
endDate?: Date;
}

View File

@ -0,0 +1,2 @@
export * from './usage.module';
export * from './usage.service';

View File

@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { PrismaModule } from '@reduced.to/prisma';
import { UsageService } from './usage.service';
@Module({
imports: [PrismaModule],
providers: [UsageService],
exports: [UsageService],
})
export class UsageModule {}

View File

@ -0,0 +1,111 @@
import { Injectable } from '@nestjs/common';
import { PrismaService, User, Subscription, Usage } from '@reduced.to/prisma';
import { PLAN_LEVELS } from '../limits';
@Injectable()
export class UsageService {
constructor(private readonly prismaService: PrismaService) {}
async getUsage(userId: string) {
return this.prismaService.usage.findUnique({
where: { userId },
});
}
async updateLimits(userId: string, plan: string) {
const planConfig = PLAN_LEVELS[plan] || PLAN_LEVELS.FREE;
return this.prismaService.usage.update({
where: { userId },
data: {
clicksLimit: planConfig.FEATURES.TRACKED_CLICKS.value,
linksLimit: planConfig.FEATURES.LINKS_COUNT.value,
},
});
}
async resetUsage(userId: string) {
return this.prismaService.usage.update({
where: { userId },
data: {
clicksCount: 0,
linksCount: 0,
},
});
}
async runOnAllActiveUsers(callback: (userId: string) => Promise<void>): Promise<void> {
const pageSize = 1000;
let page = 1;
let hasMoreUsers = true;
while (hasMoreUsers) {
try {
const users = await this.prismaService.user.findMany({
where: {
verified: true,
},
select: {
id: true,
},
skip: (page - 1) * pageSize,
take: pageSize,
});
if (users.length === 0) {
hasMoreUsers = false;
break;
}
for (const user of users) {
await callback(user.id);
}
page++;
} catch (error) {
console.error('Error fetching users:', error);
break;
}
}
}
async incrementClicksCount(userId: string) {
return this.prismaService.usage.update({
where: { userId },
data: { clicksCount: { increment: 1 } },
});
}
async incrementLinksCount(userId: string) {
return this.prismaService.usage.update({
where: { userId },
data: { linksCount: { increment: 1 } },
});
}
async decreaseLinksCount(userId: string) {
return this.prismaService.usage.update({
where: { userId },
data: { linksCount: { decrement: 1 } },
});
}
async isEligibleToCreateLink(userId: string) {
const usage = await this.getUsage(userId);
if (!usage) {
return false;
}
return usage.linksCount < usage.linksLimit;
}
async isEligibleToTrackClicks(userId: string) {
const usage = await this.getUsage(userId);
if (!usage) {
return false;
}
return usage.clicksCount < usage.clicksLimit;
}
}

View File

@ -0,0 +1 @@
export * from './lib/limits';

View File

@ -0,0 +1,16 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs"
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@ -0,0 +1,11 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"],
"include": ["src/**/*.ts"]
}

View File

@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"]
}

View File

@ -1 +1 @@
export * from './lib/';
export * from './lib';

1298
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -79,9 +79,12 @@
"@nestjs/jwt": "^10.2.0",
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.3.3",
"@nestjs/schedule": "^4.0.2",
"@nestjs/throttler": "^4.2.1",
"@novu/node": "^0.24.1",
"@origranot/ts-logger": "^1.12.0",
"@paddle/paddle-js": "^1.1.1",
"@paddle/paddle-node-sdk": "^1.3.0",
"@prisma/client": "^5.12.1",
"@qwikest/icons": "^0.0.13",
"argon2": "^0.40.1",

View File

@ -20,6 +20,7 @@
"@reduced.to/prisma": ["libs/prisma/src/index.ts"],
"@reduced.to/queue-manager": ["libs/queue-manager/src/index.ts"],
"@reduced.to/safe-url": ["libs/safe-url/src/index.ts"],
"@reduced.to/subscription-manager": ["libs/subscription-manager/src/index.ts"],
"@reduced.to/utils": ["libs/utils/src/index.ts"]
}
},