feat: initial commit of Vancouver Investment Fund web application

This commit is contained in:
sha
2025-06-20 13:53:19 +03:00
commit d1fa63d3c1
28 changed files with 3177 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
<script>
import { onMount } from 'svelte';
import router from 'svelte-spa-router';
import { theme, language } from './stores.js';
import Header from './components/Header.svelte';
import Footer from './components/Footer.svelte';
import Home from './components/Home.svelte';
// Routes configuration
const routes = {
'/': Home,
// Add more routes here in the future
};
onMount(() => {
// Initialize theme and language
theme.init();
language.init();
});
</script>
<svelte:head>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
</svelte:head>
<Header />
<main class="main-content">
<svelte:component this={router} {routes} />
</main>
<Footer />
<style>
.main-content {
min-height: calc(100vh - 8rem);
}
</style>
+138
View File
@@ -0,0 +1,138 @@
/* CSS Custom Properties for theming */
:root {
/* Light theme */
--primary-color: #2563eb;
--primary-hover: #1d4ed8;
--bg-color: #ffffff;
--bg-secondary: #f8fafc;
--bg-hover: #f1f5f9;
--text-color: #1e293b;
--text-secondary: #64748b;
--border-color: #e2e8f0;
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
}
[data-theme="dark"] {
/* Dark theme - Grey palette */
--primary-color: #9ca3af;
--primary-hover: #6b7280;
--bg-color: #111827;
--bg-secondary: #1f2937;
--bg-hover: #374151;
--text-color: #f9fafb;
--text-secondary: #d1d5db;
--border-color: #374151;
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3), 0 1px 2px 0 rgba(0, 0, 0, 0.2);
}
/* Reset and base styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: var(--text-color);
background-color: var(--bg-color);
transition: background-color 0.3s ease, color 0.3s ease;
overflow-x: hidden;
}
/* Typography */
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
line-height: 1.2;
color: var(--text-color);
}
p {
margin-bottom: 1rem;
}
a {
color: var(--primary-color);
text-decoration: none;
transition: color 0.2s ease;
}
a:hover {
color: var(--primary-hover);
}
/* Scrollbar styling */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg-secondary);
}
::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-secondary);
}
/* Focus styles for accessibility */
button:focus-visible,
a:focus-visible {
outline: 2px solid var(--primary-color);
outline-offset: 2px;
}
/* Loading animation */
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.loading {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
/* Responsive breakpoints */
@media (max-width: 640px) {
html {
font-size: 14px;
}
}
@media (min-width: 641px) and (max-width: 1024px) {
html {
font-size: 15px;
}
}
@media (min-width: 1025px) {
html {
font-size: 16px;
}
}
/* Print styles */
@media print {
* {
color: black !important;
background: white !important;
}
.header,
.footer {
display: none;
}
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="26.6" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 308"><path fill="#FF3E00" d="M239.682 40.707C211.113-.182 154.69-12.301 113.895 13.69L42.247 59.356a82.198 82.198 0 0 0-37.135 55.056a86.566 86.566 0 0 0 8.536 55.576a82.425 82.425 0 0 0-12.296 30.719a87.596 87.596 0 0 0 14.964 66.244c28.574 40.893 84.997 53.007 125.787 27.016l71.648-45.664a82.182 82.182 0 0 0 37.135-55.057a86.601 86.601 0 0 0-8.53-55.577a82.409 82.409 0 0 0 12.29-30.718a87.573 87.573 0 0 0-14.963-66.244"></path><path fill="#FFF" d="M106.889 270.841c-23.102 6.007-47.497-3.036-61.103-22.648a52.685 52.685 0 0 1-9.003-39.85a49.978 49.978 0 0 1 1.713-6.693l1.35-4.115l3.671 2.697a92.447 92.447 0 0 0 28.036 14.007l2.663.808l-.245 2.659a16.067 16.067 0 0 0 2.89 10.656a17.143 17.143 0 0 0 18.397 6.828a15.786 15.786 0 0 0 4.403-1.935l71.67-45.672a14.922 14.922 0 0 0 6.734-9.977a15.923 15.923 0 0 0-2.713-12.011a17.156 17.156 0 0 0-18.404-6.832a15.78 15.78 0 0 0-4.396 1.933l-27.35 17.434a52.298 52.298 0 0 1-14.553 6.391c-23.101 6.007-47.497-3.036-61.101-22.649a52.681 52.681 0 0 1-9.004-39.849a49.428 49.428 0 0 1 22.34-33.114l71.664-45.677a52.218 52.218 0 0 1 14.563-6.398c23.101-6.007 47.497 3.036 61.101 22.648a52.685 52.685 0 0 1 9.004 39.85a50.559 50.559 0 0 1-1.713 6.692l-1.35 4.116l-3.67-2.693a92.373 92.373 0 0 0-28.037-14.013l-2.664-.809l.246-2.658a16.099 16.099 0 0 0-2.89-10.656a17.143 17.143 0 0 0-18.398-6.828a15.786 15.786 0 0 0-4.402 1.935l-71.67 45.674a14.898 14.898 0 0 0-6.73 9.975a15.9 15.9 0 0 0 2.709 12.012a17.156 17.156 0 0 0 18.404 6.832a15.841 15.841 0 0 0 4.402-1.935l27.345-17.427a52.147 52.147 0 0 1 14.552-6.397c23.101-6.006 47.497 3.037 61.102 22.65a52.681 52.681 0 0 1 9.003 39.848a49.453 49.453 0 0 1-22.34 33.12l-71.664 45.673a52.218 52.218 0 0 1-14.563 6.398"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

+104
View File
@@ -0,0 +1,104 @@
<script>
import { language } from '../stores.js';
import { t } from '../translations.js';
</script>
<footer class="footer">
<div class="container">
<div class="footer-content">
<div class="footer-section">
<h3>Vancouver Investment Fund</h3>
<p class="footer-description">
{t('description', $language)}
</p>
</div>
<div class="footer-section">
<h4>{t('contact_title', $language)}</h4>
<div class="contact-info">
<p><strong>{t('manager_title', $language)}</strong></p>
<p>{t('address', $language)}</p>
<p>{t('phone', $language)}</p>
</div>
</div>
</div>
<div class="footer-bottom">
<p>&copy; 2025 Vancouver Investment Fund. All rights reserved.</p>
</div>
</div>
</footer>
<style>
.footer {
background: var(--bg-secondary);
border-top: 1px solid var(--border-color);
padding: 3rem 0 1rem;
margin-top: 4rem;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 2rem;
}
.footer-content {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 2rem;
margin-bottom: 2rem;
}
.footer-section h3 {
color: var(--primary-color);
margin-bottom: 1rem;
font-size: 1.25rem;
}
.footer-section h4 {
color: var(--text-color);
margin-bottom: 1rem;
font-size: 1rem;
}
.footer-description {
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 0;
}
.contact-info p {
margin: 0.5rem 0;
color: var(--text-secondary);
font-size: 0.875rem;
line-height: 1.5;
}
.footer-bottom {
border-top: 1px solid var(--border-color);
padding-top: 1rem;
text-align: center;
}
.footer-bottom p {
margin: 0;
color: var(--text-secondary);
font-size: 0.875rem;
}
@media (max-width: 768px) {
.container {
padding: 0 1rem;
}
.footer-content {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.footer {
padding: 2rem 0 1rem;
}
}
</style>
+237
View File
@@ -0,0 +1,237 @@
<script>
import { theme, language } from '../stores.js';
import { Sun, Moon, Globe, Monitor } from 'lucide-svelte';
let showLanguageMenu = false;
function cycleTheme() {
theme.cycle();
}
function setLanguage(lang) {
language.set(lang);
showLanguageMenu = false;
}
function toggleLanguageMenu() {
showLanguageMenu = !showLanguageMenu;
}
function getThemeIcon(currentTheme) {
switch (currentTheme) {
case 'light':
return Sun;
case 'dark':
return Moon;
case 'system':
default:
return Monitor;
}
}
function getThemeLabel(currentTheme) {
switch (currentTheme) {
case 'light':
return 'Light';
case 'dark':
return 'Dark';
case 'system':
default:
return 'System';
}
}
</script>
<header class="header">
<div class="container">
<div class="header-content">
<div class="logo">
<h1>Vancouver</h1>
</div>
<div class="header-controls">
<!-- Language Switcher -->
<div class="language-switcher" class:active={showLanguageMenu}>
<button
class="control-btn language-btn"
on:click={toggleLanguageMenu}
aria-label="Change language"
>
<Globe size={20} />
<span class="current-lang">{$language.toUpperCase()}</span>
</button>
{#if showLanguageMenu}
<div class="language-menu">
<button on:click={() => setLanguage('en')} class:active={$language === 'en'}>
English
</button>
<button on:click={() => setLanguage('uk')} class:active={$language === 'uk'}>
Українська
</button>
</div>
{/if}
</div>
<!-- Theme Switcher -->
<button
class="control-btn theme-btn"
on:click={cycleTheme}
aria-label="Change theme mode"
title="Current: {getThemeLabel($theme)}"
>
<svelte:component this={getThemeIcon($theme)} size={20} />
<span class="theme-label">{getThemeLabel($theme)}</span>
</button>
</div>
</div>
</div>
</header>
<style>
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
background: var(--bg-color);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--border-color);
z-index: 1000;
transition: all 0.3s ease;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 2rem;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
height: 4rem;
}
.logo h1 {
margin: 0;
font-size: 1.5rem;
font-weight: 700;
color: var(--primary-color);
transition: color 0.3s ease;
}
.header-controls {
display: flex;
align-items: center;
gap: 0.5rem;
}
.control-btn {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 0.5rem;
color: var(--text-color);
cursor: pointer;
transition: all 0.3s ease;
font-size: 0.875rem;
}
.control-btn:hover {
background: var(--bg-hover);
transform: translateY(-1px);
}
.language-switcher {
position: relative;
}
.language-btn {
min-width: 4rem;
}
.theme-btn {
min-width: 5rem;
}
.current-lang {
font-weight: 500;
font-size: 0.75rem;
}
.theme-label {
font-weight: 500;
font-size: 0.75rem;
}
.language-menu {
position: absolute;
top: calc(100% + 0.5rem);
right: 0;
background: var(--bg-color);
border: 1px solid var(--border-color);
border-radius: 0.5rem;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
overflow: hidden;
animation: slideDown 0.2s ease;
min-width: 120px;
}
.language-menu button {
display: block;
width: 100%;
padding: 0.75rem 1rem;
background: none;
border: none;
color: var(--text-color);
text-align: left;
cursor: pointer;
transition: background-color 0.2s ease;
font-size: 0.875rem;
}
.language-menu button:hover {
background: var(--bg-hover);
}
.language-menu button.active {
background: var(--primary-color);
color: white;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (max-width: 768px) {
.container {
padding: 0 1rem;
}
.logo h1 {
font-size: 1.25rem;
}
.current-lang,
.theme-label {
display: none;
}
.language-btn,
.theme-btn {
min-width: auto;
}
}
</style>
+473
View File
@@ -0,0 +1,473 @@
<script>
import { onMount } from 'svelte';
import { language } from '../stores.js';
import { t } from '../translations.js';
import { TrendingUp, Building2, TreePine, ShoppingCart, Truck, Users } from 'lucide-svelte';
let mounted = false;
onMount(() => {
mounted = true;
// Intersection Observer for scroll animations
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animate-in');
}
});
}, {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
});
const sections = document.querySelectorAll('.scroll-animate');
sections.forEach(section => observer.observe(section));
return () => {
sections.forEach(section => observer.unobserve(section));
};
});
const investmentAreas = [
{
icon: TrendingUp,
titleKey: 'energy',
descKey: 'energy_desc',
color: '#059669'
},
{
icon: TreePine,
titleKey: 'woodworking',
descKey: 'woodworking_desc',
color: '#7c3aed'
},
{
icon: Building2,
titleKey: 'real_estate',
descKey: 'real_estate_desc',
color: '#d97706'
},
{
icon: ShoppingCart,
titleKey: 'retail',
descKey: 'retail_desc',
color: '#dc2626'
},
{
icon: Truck,
titleKey: 'wholesale',
descKey: 'wholesale_desc',
color: '#4f46e5'
}
];
</script>
<main class="main" class:mounted>
<!-- Hero Section -->
<section class="hero scroll-animate">
<div class="container">
<div class="hero-content">
<h1 class="hero-title">
{t('company_name', $language)}
</h1>
<p class="hero-description">
{t('description', $language)}
</p>
<div class="nav-value">
<strong>{t('nav_value', $language)}</strong>
</div>
</div>
</div>
</section>
<!-- Investment Areas -->
<section class="investment-areas scroll-animate">
<div class="container">
<div class="areas-grid">
{#each investmentAreas as area, index}
<div class="area-card" style="--delay: {index * 0.1}s">
<div class="area-icon" style="--icon-color: {area.color}">
<svelte:component this={area.icon} size={32} />
</div>
<h3>{t(area.titleKey, $language)}</h3>
<p>{t(area.descKey, $language)}</p>
</div>
{/each}
</div>
</div>
</section>
<!-- Mission & Vision -->
<section class="mission-vision scroll-animate">
<div class="container">
<h2 class="section-title">{t('mission_title', $language)}</h2>
<div class="mv-grid">
<div class="mv-card">
<h3>{t('mission', $language)}</h3>
<p>{t('mission_desc', $language)}</p>
</div>
<div class="mv-card">
<h3>{t('vision', $language)}</h3>
<p>{t('vision_desc', $language)}</p>
</div>
</div>
</div>
</section>
<!-- Team -->
<section class="team scroll-animate">
<div class="container">
<h2 class="section-title">{t('team_title', $language)}</h2>
<p class="team-description">{t('team_desc', $language)}</p>
<div class="team-card">
<div class="team-icon">
<Users size={32} />
</div>
<div class="team-info">
<h4>{t('fund_manager', $language)}</h4>
<p>{t('manager_name', $language)}</p>
</div>
</div>
</div>
</section>
<!-- Partners -->
<section class="partners scroll-animate">
<div class="container">
<h2 class="section-title">{t('partners_title', $language)}</h2>
<div class="partners-grid">
<div class="partner-card">
<h4>{t('auditing_firm', $language)}</h4>
<p><strong>{t('auditing_name', $language)}</strong></p>
<p>{t('auditing_address', $language)}</p>
<p>{t('auditing_code', $language)}</p>
</div>
<div class="partner-card">
<h4>{t('depository', $language)}</h4>
<p><strong>{t('depository_name', $language)}</strong></p>
<p>{t('depository_address', $language)}</p>
<p>{t('depository_code', $language)}</p>
</div>
<div class="partner-card">
<h4>{t('custodian', $language)}</h4>
<p><strong>{t('custodian_name', $language)}</strong></p>
<p>{t('custodian_address', $language)}</p>
<p>{t('custodian_code', $language)}</p>
</div>
<div class="partner-card">
<h4>{t('valuer', $language)}</h4>
<p><strong>{t('valuer_name', $language)}</strong></p>
<p>{t('valuer_address', $language)}</p>
<p>{t('valuer_code', $language)}</p>
</div>
</div>
</div>
</section>
</main>
<style>
.main {
padding-top: 4rem;
opacity: 0;
transition: opacity 0.6s ease;
}
.main.mounted {
opacity: 1;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 2rem;
}
.scroll-animate {
opacity: 0;
transform: translateY(30px);
transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1);
}
:global(.scroll-animate.animate-in) {
opacity: 1;
transform: translateY(0);
}
/* Hero Section */
.hero {
padding: 4rem 0;
background: linear-gradient(135deg, var(--bg-color) 0%, var(--bg-secondary) 100%);
}
.hero-title {
font-size: clamp(1.5rem, 4vw, 2.5rem);
font-weight: 700;
color: var(--primary-color);
margin-bottom: 1.5rem;
line-height: 1.2;
text-align: center;
}
.hero-description {
font-size: 1.125rem;
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 2rem;
text-align: center;
max-width: 800px;
margin-left: auto;
margin-right: auto;
}
.nav-value {
text-align: center;
padding: 1.5rem;
background: var(--bg-color);
border: 2px solid var(--primary-color);
border-radius: 1rem;
font-size: 1.125rem;
color: var(--primary-color);
max-width: 600px;
margin: 0 auto;
}
/* Investment Areas */
.investment-areas {
padding: 4rem 0;
}
.areas-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin-top: 2rem;
}
.area-card {
background: var(--bg-color);
border: 1px solid var(--border-color);
border-radius: 1rem;
padding: 2rem;
text-align: center;
transition: all 0.3s ease;
animation: fadeInUp 0.8s ease forwards;
animation-delay: var(--delay);
opacity: 0;
transform: translateY(20px);
}
.area-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.area-icon {
width: 64px;
height: 64px;
background: var(--icon-color);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1.5rem;
color: white;
}
.area-card h3 {
color: var(--text-color);
margin-bottom: 1rem;
font-size: 1.25rem;
}
.area-card p {
color: var(--text-secondary);
line-height: 1.6;
}
/* Mission & Vision */
.mission-vision {
padding: 4rem 0;
background: var(--bg-secondary);
}
.section-title {
font-size: 2rem;
font-weight: 700;
color: var(--text-color);
text-align: center;
margin-bottom: 3rem;
}
.mv-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 2rem;
}
.mv-card {
background: var(--bg-color);
border: 1px solid var(--border-color);
border-radius: 1rem;
padding: 2rem;
transition: all 0.3s ease;
}
.mv-card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.mv-card h3 {
color: var(--primary-color);
margin-bottom: 1rem;
font-size: 1.5rem;
}
.mv-card p {
color: var(--text-secondary);
line-height: 1.6;
}
/* Team */
.team {
padding: 4rem 0;
}
.team-description {
text-align: center;
color: var(--text-secondary);
font-size: 1.125rem;
margin-bottom: 2rem;
}
.team-card {
display: flex;
align-items: center;
gap: 2rem;
background: var(--bg-color);
border: 1px solid var(--border-color);
border-radius: 1rem;
padding: 2rem;
max-width: 600px;
margin: 0 auto;
transition: all 0.3s ease;
}
.team-card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.team-icon {
width: 64px;
height: 64px;
background: var(--primary-color);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
flex-shrink: 0;
}
.team-info h4 {
color: var(--text-color);
margin-bottom: 0.5rem;
font-size: 1.125rem;
}
.team-info p {
color: var(--text-secondary);
line-height: 1.6;
margin: 0;
}
/* Partners */
.partners {
padding: 4rem 0;
background: var(--bg-secondary);
}
.partners-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
margin-top: 2rem;
}
.partner-card {
background: var(--bg-color);
border: 1px solid var(--border-color);
border-radius: 1rem;
padding: 1.5rem;
transition: all 0.3s ease;
}
.partner-card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.partner-card h4 {
color: var(--primary-color);
margin-bottom: 1rem;
font-size: 1rem;
}
.partner-card p {
color: var(--text-secondary);
font-size: 0.875rem;
line-height: 1.5;
margin: 0.5rem 0;
}
.partner-card p strong {
color: var(--text-color);
}
@keyframes fadeInUp {
to {
opacity: 1;
transform: translateY(0);
}
}
@media (max-width: 768px) {
.container {
padding: 0 1rem;
}
.hero {
padding: 2rem 0;
}
.section-title {
font-size: 1.5rem;
}
.areas-grid {
grid-template-columns: 1fr;
}
.mv-grid {
grid-template-columns: 1fr;
}
.team-card {
flex-direction: column;
text-align: center;
}
.partners-grid {
grid-template-columns: 1fr;
}
}
</style>
+10
View File
@@ -0,0 +1,10 @@
<script>
let count = $state(0)
const increment = () => {
count += 1
}
</script>
<button onclick={increment}>
count is {count}
</button>
+9
View File
@@ -0,0 +1,9 @@
import { mount } from 'svelte'
import './app.css'
import App from './App.svelte'
const app = mount(App, {
target: document.getElementById('app'),
})
export default app
+90
View File
@@ -0,0 +1,90 @@
import { writable } from 'svelte/store';
const browser = typeof window !== 'undefined';
// Theme store with three modes: system, light, dark
function createThemeStore() {
const { subscribe, set, update } = writable('system');
function applyTheme(mode) {
if (!browser) return;
let actualTheme;
if (mode === 'system') {
actualTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
} else {
actualTheme = mode;
}
document.documentElement.setAttribute('data-theme', actualTheme);
}
return {
subscribe,
set: (theme) => {
if (browser) {
localStorage.setItem('theme', theme);
applyTheme(theme);
}
set(theme);
},
init: () => {
if (browser) {
const saved = localStorage.getItem('theme') || 'system';
applyTheme(saved);
set(saved);
// Listen for system theme changes when in system mode
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', () => {
const currentTheme = localStorage.getItem('theme') || 'system';
if (currentTheme === 'system') {
applyTheme('system');
}
});
}
},
cycle: () => {
update(current => {
const modes = ['system', 'light', 'dark'];
const currentIndex = modes.indexOf(current);
const nextMode = modes[(currentIndex + 1) % modes.length];
if (browser) {
localStorage.setItem('theme', nextMode);
applyTheme(nextMode);
}
return nextMode;
});
}
};
}
// Language store
function createLanguageStore() {
const { subscribe, set, update } = writable('en');
return {
subscribe,
set: (lang) => {
if (browser) {
localStorage.setItem('language', lang);
document.documentElement.setAttribute('lang', lang);
}
set(lang);
},
init: () => {
if (browser) {
const saved = localStorage.getItem('language');
const browserLang = navigator.language.split('-')[0];
const supportedLangs = ['en', 'uk', 'ru'];
const lang = saved || (supportedLangs.includes(browserLang) ? browserLang : 'en');
document.documentElement.setAttribute('lang', lang);
set(lang);
}
}
};
}
export const theme = createThemeStore();
export const language = createLanguageStore();
+98
View File
@@ -0,0 +1,98 @@
export const translations = {
en: {
company_name: 'JOINT STOCK COMPANY CLOSED NON-DIVERSIFIED VENTURE CORPORATE INVESTMENT FUND "VANCOUVER"',
description: 'We are a Ukrainian investment fund established in 2018. Over more than six years of operation, we have focused our investment strategy on the following key areas:',
energy: 'Energy',
energy_desc: 'Investments in renewable energy, particularly in the Hirskyi Wind Park project, which provides clean energy and contributes to Ukraine\'s energy independence.',
woodworking: 'Woodworking Industry',
woodworking_desc: 'Partnership with SLAV FOREST — a modern woodworking enterprise that implements advanced wood processing technologies with a focus on export-oriented production.',
real_estate: 'Office Real Estate',
real_estate_desc: 'We invest in stable real estate assets located in the central business districts of Kyiv.',
retail: 'Retail',
retail_desc: 'Investment participation in the development of the Silpo chain — one of Ukraine\'s most successful retailers, focused on innovation in customer service.',
wholesale: 'Wholesale Trade',
wholesale_desc: 'Support of trading companies that provide logistics and distribution of goods both within Ukraine and internationally.',
nav_value: 'As of the end of Q1 2025, the Fund\'s net asset value amounts to €16.8 million.',
mission_title: 'Mission / Vision',
mission: 'Mission',
mission_desc: 'To generate stable long-term capital growth through strategic investments in promising economic sectors demonstrating sustainable development dynamics.',
vision: 'Vision',
vision_desc: 'We strive to become the investment partner of choice for Ukrainian and international investors seeking transparent, professional, and efficient capital placement mechanisms.',
team_title: 'Team',
team_desc: 'Key person responsible for the Fund\'s operations:',
fund_manager: 'Fund Manager:',
manager_name: 'Oleksandr Viktorovych Svynar — an experienced finance and investment manager responsible for the overall strategy and asset management.',
partners_title: 'Fund Partners',
auditing_firm: 'Auditing firm:',
auditing_name: 'LLC "AUDITING CENTER "INFORM-PLUS"',
auditing_address: 'Kyiv, 52 Bohdana Khmelnytskoho St., Office 504',
auditing_code: 'EDRPOU Code: 31984899',
depository: 'Depository:',
depository_name: 'JSC "NATIONAL DEPOSITORY OF UKRAINE"',
depository_address: 'Kyiv, 7-G Tropinina St.',
depository_code: 'EDRPOU Code: 30370711',
custodian: 'Custodian Institution:',
custodian_name: 'LLC "INZHUR CAPITAL"',
custodian_address: 'Kyiv, 48, 50A Zhylianska St., 7th Floor, Office 19',
custodian_code: 'EDRPOU Code: 36301402',
valuer: 'Property Valuer:',
valuer_name: 'LLC "EXPERT-ALLIANCE"',
valuer_address: 'Kyiv, 25 Lva Tolstoho St.',
valuer_code: 'EDRPOU Code: 34289017',
contact_title: 'Contact Information',
manager_title: 'Manager: Oleksandr Viktorovych Svynar',
office_address: 'Office Address:',
address: '01033, Ukraine, Kyiv, 48, 50A Zhylianska St., Office 13',
phone: 'Phone: +38 050 465 46 18'
},
uk: {
company_name: 'АКЦІОНЕРНЕ ТОВАРИСТВО ЗАКРИТИЙ НЕДИВЕРСИФІКОВАНИЙ ВЕНЧУРНИЙ КОРПОРАТИВНИЙ ІНВЕСТИЦІЙНИЙ ФОНД "ВАНКУВЕР"',
description: 'Ми — український інвестиційний фонд, заснований у 2018 році. За понад шість років роботи ми зосередили свою інвестиційну стратегію на наступних ключових напрямках:',
energy: 'Енергетика',
energy_desc: 'Інвестиції у відновлювану енергетику, зокрема у проект Гірського вітропарку, який забезпечує чисту енергію та сприяє енергетичній незалежності України.',
woodworking: 'Деревообробна промисловість',
woodworking_desc: 'Партнерство з SLAV FOREST — сучасним деревообробним підприємством, яке впроваджує передові технології обробки деревини з орієнтацією на експортне виробництво.',
real_estate: 'Офісна нерухомість',
real_estate_desc: 'Ми інвестуємо у стабільні активи нерухомості, розташовані в центральних діловий районах Києва.',
retail: 'Роздрібна торгівля',
retail_desc: 'Інвестиційна участь у розвитку мережі Silpo — одного з найуспішніших рітейлерів України, орієнтованого на інновації в обслуговуванні клієнтів.',
wholesale: 'Оптова торгівля',
wholesale_desc: 'Підтримка торговельних компаній, які забезпечують логістику та дистрибуцію товарів як в межах України, так і на міжнародному рівні.',
nav_value: 'Станом на кінець І кварталу 2025 року чиста вартість активів Фонду становить 16,8 млн євро.',
mission_title: 'Місія / Бачення',
mission: 'Місія',
mission_desc: 'Генерувати стабільне довгострокове зростання капіталу через стратегічні інвестиції у перспективні економічні сектори, що демонструють динаміку сталого розвитку.',
vision: 'Бачення',
vision_desc: 'Ми прагнемо стати інвестиційним партнером вибору для українських та міжнародних інвесторів, які шукають прозорі, професійні та ефективні механізми розміщення капіталу.',
team_title: 'Команда',
team_desc: 'Ключова особа, відповідальна за діяльність Фонду:',
fund_manager: 'Управляючий фондом:',
manager_name: 'Свинар Олександр Вікторович — досвідчений менеджер з фінансів та інвестицій, відповідальний за загальну стратегію та управління активами.',
partners_title: 'Партнери фонду',
auditing_firm: 'Аудиторська фірма:',
auditing_name: 'ТОВ "АУДИТОРСЬКИЙ ЦЕНТР "ІНФОРМ-ПЛЮС"',
auditing_address: 'м. Київ, вул. Богдана Хмельницького, 52, офіс 504',
auditing_code: 'Код ЄДРПОУ: 31984899',
depository: 'Депозитарій:',
depository_name: 'АТ "НАЦІОНАЛЬНИЙ ДЕПОЗИТАРІЙ УКРАЇНИ"',
depository_address: 'м. Київ, вул. Тропініна, 7-Г',
depository_code: 'Код ЄДРПОУ: 30370711',
custodian: 'Зберігач:',
custodian_name: 'ТОВ "ІНЖУРА КАПІТАЛ"',
custodian_address: 'м. Київ, вул. Жилянська, 48, 50А, 7-й поверх, офіс 19',
custodian_code: 'Код ЄДРПОУ: 36301402',
valuer: 'Оцінювач майна:',
valuer_name: 'ТОВ "ЕКСПЕРТ-АЛЬЯНС"',
valuer_address: 'м. Київ, вул. Льва Толстого, 25',
valuer_code: 'Код ЄДРПОУ: 34289017',
contact_title: 'Контактна інформація',
manager_title: 'Менеджер: Свинар Олександр Вікторович',
office_address: 'Адреса офісу:',
address: '01033, Україна, м. Київ, вул. Жилянська, 48, 50А, офіс 13',
phone: 'Телефон: +38 050 465 46 18'
}
};
export function t(key, lang) {
return translations[lang]?.[key] || translations.en[key] || key;
}
+2
View File
@@ -0,0 +1,2 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />