diff --git a/.gitignore b/.gitignore
index 035d487..4dee025 100644
--- a/.gitignore
+++ b/.gitignore
@@ -125,4 +125,6 @@ _modules/*
/results/
/phpunit*.xml
.env
-env
\ No newline at end of file
+env
+.claude/
+.claude/*
\ No newline at end of file
diff --git a/app/Config/Routes.php b/app/Config/Routes.php
index fc4914a..3d91166 100644
--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -6,3 +6,9 @@ use CodeIgniter\Router\RouteCollection;
* @var RouteCollection $routes
*/
$routes->get('/', 'Home::index');
+<<<<<<< Updated upstream
+=======
+$routes->get('/themes', 'ThemeStore::index');
+$routes->post('/themes/upload', 'ThemeStore::upload');
+$routes->get('/themes/preview/(:segment)', 'ThemeStore::preview/$1');
+>>>>>>> Stashed changes
diff --git a/app/Controllers/ThemeStore.php b/app/Controllers/ThemeStore.php
new file mode 100644
index 0000000..e30ad17
--- /dev/null
+++ b/app/Controllers/ThemeStore.php
@@ -0,0 +1,127 @@
+where('is_published', 1)->findAll();
+
+ foreach ($themes as &$theme) {
+ $meta = json_decode($theme['metadata'] ?? '{}', true);
+ $theme['colors'] = $meta['colors'] ?? [];
+ $theme['tags'] = $meta['tags'] ?? [];
+ $theme['vars'] = $meta['vars'] ?? [];
+ }
+
+ return view('theme_store', [
+ 'themes' => $themes,
+ 'flash_success' => session()->getFlashdata('success'),
+ 'flash_error' => session()->getFlashdata('error'),
+ ]);
+ }
+
+ public function upload(): Response
+ {
+ $file = $this->request->getFile('theme_css');
+ $displayName = trim($this->request->getPost('display_name') ?? '');
+ $description = trim($this->request->getPost('description') ?? '');
+
+ if ($displayName === '') {
+ return redirect()->to('/themes')->with('error', 'Display name is required.');
+ }
+
+ if (! $file || ! $file->isValid() || $file->hasMoved()) {
+ return redirect()->to('/themes')->with('error', 'Please upload a valid CSS file.');
+ }
+
+ if (strtolower($file->getExtension()) !== 'css') {
+ return redirect()->to('/themes')->with('error', 'Only .css files are allowed.');
+ }
+
+ $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $displayName));
+ $slug = trim($slug, '-');
+ $filename = $slug . '-' . substr(bin2hex(random_bytes(3)), 0, 6) . '.css';
+
+ $file->move(FCPATH . 'themes', $filename, true);
+
+ $model = new MarketplaceThemeModel();
+ $model->insert([
+ 'id' => $this->uuid4(),
+ 'name' => $slug,
+ 'display_name' => $displayName,
+ 'description' => $description ?: 'Custom community theme.',
+ 'author' => 'Community',
+ 'version' => '1.0.0',
+ 'thumbnail_url' => null,
+ 'download_url' => '/themes/' . $filename,
+ 'price' => 0,
+ 'is_published' => true,
+ 'metadata' => json_encode(['tags' => ['custom', 'community'], 'colors' => []]),
+ 'created_at' => date('Y-m-d H:i:s'),
+ 'updated_at' => date('Y-m-d H:i:s'),
+ ]);
+
+ return redirect()->to('/themes')->with('success', '"' . esc($displayName) . '" uploaded successfully!');
+ }
+
+ public function preview(string $id): Response
+ {
+ $model = new MarketplaceThemeModel();
+ $theme = $model->find($id);
+
+ if (! $theme) {
+ return $this->response->setStatusCode(404)->setBody('
Theme not found.
');
+ }
+
+ $distIndex = '/home/came/Nextcloud/arch-work/Projects/Todo-App/dist/index.html';
+
+ if (! file_exists($distIndex)) {
+ return $this->response->setBody(
+ ''
+ . ''
+ );
+ }
+
+ $todoHtml = file_get_contents($distIndex);
+
+ // Rewrite asset paths from /assets/ to the public symlink so Apache serves them
+ $assetBase = rtrim(base_url('todo-preview'), '/');
+ $todoHtml = str_replace('="/assets/', '="' . $assetBase . '/assets/', $todoHtml);
+
+ // Build CSS variable overrides from the stored vars map
+ $meta = json_decode($theme['metadata'] ?? '{}', true);
+ $vars = $meta['vars'] ?? [];
+
+ $cssVars = ":root {\n";
+ foreach ($vars as $prop => $value) {
+ $cssVars .= " {$prop}: {$value};\n";
+ }
+ $cssVars .= "}\n";
+
+ // Also inject any raw CSS from the downloaded file (for custom/uploaded themes)
+ $cssPath = FCPATH . ltrim($theme['download_url'], '/');
+ $rawCss = file_exists($cssPath) ? file_get_contents($cssPath) : '';
+
+ $styleTag = "";
+
+ $todoHtml = str_replace('', $styleTag . "\n", $todoHtml);
+
+ return $this->response
+ ->setHeader('Content-Type', 'text/html; charset=utf-8')
+ ->setBody($todoHtml);
+ }
+
+ private function uuid4(): string
+ {
+ $data = random_bytes(16);
+ $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
+ $data[8] = chr(ord($data[8]) & 0x3f | 0x80);
+ return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
+ }
+}
diff --git a/app/Database/Seeds/MarketplaceThemesSeeder.php b/app/Database/Seeds/MarketplaceThemesSeeder.php
index 9d26bc3..5933fdb 100644
--- a/app/Database/Seeds/MarketplaceThemesSeeder.php
+++ b/app/Database/Seeds/MarketplaceThemesSeeder.php
@@ -8,34 +8,296 @@ class MarketplaceThemesSeeder extends Seeder
{
public function run()
{
+ $this->db->query('SET FOREIGN_KEY_CHECKS=0');
+ $this->db->table('marketplace_themes')->truncate();
+ $this->db->query('SET FOREIGN_KEY_CHECKS=1');
+
$data = [
[
- 'id' => '550e8400-e29b-41d4-a716-446655440010',
- 'name' => 'default-light',
- 'display_name' => 'Default Light',
- 'description' => 'Clean and simple light theme',
- 'author' => 'System',
- 'version' => '1.0.0',
+ 'id' => '550e8400-e29b-41d4-a716-446655440010',
+ 'name' => 'ocean-breeze',
+ 'display_name' => 'Ocean Breeze',
+ 'description' => 'A refreshing light theme inspired by the open sea. Soft teals and ocean blues create a calm, productive workspace that\'s easy on the eyes during long work sessions.',
+ 'author' => 'ThemeForge',
+ 'version' => '1.2.0',
'thumbnail_url' => null,
- 'download_url' => '/themes/default-light.zip',
- 'price' => 0,
+ 'download_url' => '/themes/ocean-breeze.css',
+ 'price' => 0,
'is_published' => true,
- 'metadata' => json_encode(['tags' => ['light', 'clean']]),
+ 'metadata' => json_encode([
+ 'tags' => ['light', 'blue', 'calm', 'minimal'],
+ 'colors' => [
+ 'Primary' => '#0077B6',
+ 'Secondary' => '#00B4D8',
+ 'Background' => '#E0F4FF',
+ 'Surface' => '#FFFFFF',
+ 'Text' => '#1A2B3C',
+ 'Accent' => '#48CAE4',
+ ],
+ 'vars' => [
+ '--bg' => '#E0F4FF',
+ '--surface' => '#FFFFFF',
+ '--surface-strong' => '#FFFFFF',
+ '--surface-muted' => '#F0F9FF',
+ '--border' => '#BAE0F2',
+ '--line' => '#90C8E0',
+ '--text' => '#1A2B3C',
+ '--text-muted' => '#4A6B7A',
+ '--text-strong' => '#0D1B26',
+ '--accent' => '#0077B6',
+ '--accent-text' => '#FFFFFF',
+ '--accent-soft' => '#CCE9F5',
+ '--sidebar-bg' => '#FFFFFF',
+ '--sidebar-border' => '#BAE0F2',
+ '--sidebar-text' => '#1A2B3C',
+ '--sidebar-text-muted' => '#4A6B7A',
+ '--input-bg' => '#FFFFFF',
+ '--input-border' => '#BAE0F2',
+ '--modal-bg' => '#FFFFFF',
+ '--chip' => '#C8E8F0',
+ '--success' => '#D4F0E4',
+ ],
+ ]),
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
],
[
- 'id' => '550e8400-e29b-41d4-a716-446655440011',
- 'name' => 'default-dark',
- 'display_name' => 'Default Dark',
- 'description' => 'Dark theme for night owls',
- 'author' => 'System',
- 'version' => '1.0.0',
+ 'id' => '550e8400-e29b-41d4-a716-446655440011',
+ 'name' => 'midnight-void',
+ 'display_name' => 'Midnight Void',
+ 'description' => 'Deep space dark theme for night owls and late-night coders. Rich dark purples and blues with vibrant neon accents give this theme a premium, modern feel.',
+ 'author' => 'ThemeForge',
+ 'version' => '2.0.1',
'thumbnail_url' => null,
- 'download_url' => '/themes/default-dark.zip',
- 'price' => 0,
+ 'download_url' => '/themes/midnight-void.css',
+ 'price' => 0,
'is_published' => true,
- 'metadata' => json_encode(['tags' => ['dark', 'night']]),
+ 'metadata' => json_encode([
+ 'tags' => ['dark', 'purple', 'neon', 'night'],
+ 'colors' => [
+ 'Primary' => '#7C3AED',
+ 'Secondary' => '#A78BFA',
+ 'Background' => '#0D0D1A',
+ 'Surface' => '#1A1A2E',
+ 'Text' => '#E2E8F0',
+ 'Accent' => '#F472B6',
+ ],
+ 'vars' => [
+ '--bg' => '#0D0D1A',
+ '--surface' => '#1A1A2E',
+ '--surface-strong' => '#222234',
+ '--surface-muted' => '#121220',
+ '--border' => '#2A2A44',
+ '--line' => '#333350',
+ '--text' => '#E2E8F0',
+ '--text-muted' => '#94A3B8',
+ '--text-strong' => '#F1F5F9',
+ '--accent' => '#7C3AED',
+ '--accent-text' => '#FFFFFF',
+ '--accent-soft' => '#2D1A5E',
+ '--sidebar-bg' => '#16162A',
+ '--sidebar-border' => '#2A2A44',
+ '--sidebar-text' => '#E2E8F0',
+ '--sidebar-text-muted' => '#94A3B8',
+ '--input-bg' => '#0D0D1A',
+ '--input-border' => '#2A2A44',
+ '--modal-bg' => '#1A1A2E',
+ '--chip' => '#2A2A44',
+ '--success' => '#0D2A1A',
+ ],
+ ]),
+ 'created_at' => date('Y-m-d H:i:s'),
+ 'updated_at' => date('Y-m-d H:i:s'),
+ ],
+ [
+ 'id' => '550e8400-e29b-41d4-a716-446655440012',
+ 'name' => 'forest-grove',
+ 'display_name' => 'Forest Grove',
+ 'description' => 'Earthy greens and warm neutrals bring the tranquility of a woodland retreat to your workspace. A grounding, nature-inspired theme designed for focused productivity.',
+ 'author' => 'NaturePalette',
+ 'version' => '1.0.5',
+ 'thumbnail_url' => null,
+ 'download_url' => '/themes/forest-grove.css',
+ 'price' => 0,
+ 'is_published' => true,
+ 'metadata' => json_encode([
+ 'tags' => ['light', 'green', 'earthy', 'nature'],
+ 'colors' => [
+ 'Primary' => '#2D6A4F',
+ 'Secondary' => '#52B788',
+ 'Background' => '#F0F7EE',
+ 'Surface' => '#FFFFFF',
+ 'Text' => '#1B2E22',
+ 'Accent' => '#B7E4C7',
+ ],
+ 'vars' => [
+ '--bg' => '#F0F7EE',
+ '--surface' => '#FFFFFF',
+ '--surface-strong' => '#FFFFFF',
+ '--surface-muted' => '#F5FAF4',
+ '--border' => '#C0DACB',
+ '--line' => '#A0C4B0',
+ '--text' => '#1B2E22',
+ '--text-muted' => '#527A62',
+ '--text-strong' => '#0D1F14',
+ '--accent' => '#2D6A4F',
+ '--accent-text' => '#FFFFFF',
+ '--accent-soft' => '#C0E8D4',
+ '--sidebar-bg' => '#FFFFFF',
+ '--sidebar-border' => '#C0DACB',
+ '--sidebar-text' => '#1B2E22',
+ '--sidebar-text-muted' => '#527A62',
+ '--input-bg' => '#FFFFFF',
+ '--input-border' => '#C0DACB',
+ '--modal-bg' => '#FFFFFF',
+ '--chip' => '#B8E0C8',
+ '--success' => '#CCF0DC',
+ ],
+ ]),
+ 'created_at' => date('Y-m-d H:i:s'),
+ 'updated_at' => date('Y-m-d H:i:s'),
+ ],
+ [
+ 'id' => '550e8400-e29b-41d4-a716-446655440013',
+ 'name' => 'sunset-ember',
+ 'display_name' => 'Sunset Ember',
+ 'description' => 'Warm oranges, deep reds, and golden highlights capture the magic of a perfect sunset. This vibrant theme adds energy and warmth to every interaction.',
+ 'author' => 'ChromaCraft',
+ 'version' => '1.1.2',
+ 'thumbnail_url' => null,
+ 'download_url' => '/themes/sunset-ember.css',
+ 'price' => 0,
+ 'is_published' => true,
+ 'metadata' => json_encode([
+ 'tags' => ['warm', 'orange', 'vibrant', 'sunset'],
+ 'colors' => [
+ 'Primary' => '#D62828',
+ 'Secondary' => '#F77F00',
+ 'Background' => '#FFF5E4',
+ 'Surface' => '#FFFFFF',
+ 'Text' => '#2D1B00',
+ 'Accent' => '#FCBF49',
+ ],
+ 'vars' => [
+ '--bg' => '#FFF5E4',
+ '--surface' => '#FFFFFF',
+ '--surface-strong' => '#FFFFFF',
+ '--surface-muted' => '#FFF8F0',
+ '--border' => '#F0D0A8',
+ '--line' => '#E0B880',
+ '--text' => '#2D1B00',
+ '--text-muted' => '#8A6040',
+ '--text-strong' => '#1A0A00',
+ '--accent' => '#D62828',
+ '--accent-text' => '#FFFFFF',
+ '--accent-soft' => '#FFE0CC',
+ '--sidebar-bg' => '#FFFFFF',
+ '--sidebar-border' => '#F0D0A8',
+ '--sidebar-text' => '#2D1B00',
+ '--sidebar-text-muted' => '#8A6040',
+ '--input-bg' => '#FFFFFF',
+ '--input-border' => '#F0D0A8',
+ '--modal-bg' => '#FFFFFF',
+ '--chip' => '#F8D8B0',
+ '--success' => '#DDFADC',
+ ],
+ ]),
+ 'created_at' => date('Y-m-d H:i:s'),
+ 'updated_at' => date('Y-m-d H:i:s'),
+ ],
+ [
+ 'id' => '550e8400-e29b-41d4-a716-446655440014',
+ 'name' => 'arctic-frost',
+ 'display_name' => 'Arctic Frost',
+ 'description' => 'Ultra-clean whites and icy blues inspired by frozen tundras. A minimalist theme that maximises clarity and focus with crisp contrast and breathable spacing.',
+ 'author' => 'MinimalStudio',
+ 'version' => '3.0.0',
+ 'thumbnail_url' => null,
+ 'download_url' => '/themes/arctic-frost.css',
+ 'price' => 0,
+ 'is_published' => true,
+ 'metadata' => json_encode([
+ 'tags' => ['light', 'minimal', 'clean', 'ice'],
+ 'colors' => [
+ 'Primary' => '#2176AE',
+ 'Secondary' => '#57C4E5',
+ 'Background' => '#F8FBFF',
+ 'Surface' => '#FFFFFF',
+ 'Text' => '#1C2B3A',
+ 'Accent' => '#A8DADC',
+ ],
+ 'vars' => [
+ '--bg' => '#F8FBFF',
+ '--surface' => '#FFFFFF',
+ '--surface-strong' => '#FFFFFF',
+ '--surface-muted' => '#F0F5FC',
+ '--border' => '#C0D4E8',
+ '--line' => '#A0BCDA',
+ '--text' => '#1C2B3A',
+ '--text-muted' => '#4E6478',
+ '--text-strong' => '#0D1B2A',
+ '--accent' => '#2176AE',
+ '--accent-text' => '#FFFFFF',
+ '--accent-soft' => '#CCE0F0',
+ '--sidebar-bg' => '#FFFFFF',
+ '--sidebar-border' => '#C0D4E8',
+ '--sidebar-text' => '#1C2B3A',
+ '--sidebar-text-muted' => '#4E6478',
+ '--input-bg' => '#FFFFFF',
+ '--input-border' => '#C0D4E8',
+ '--modal-bg' => '#FFFFFF',
+ '--chip' => '#B8D4E8',
+ '--success' => '#D4F0E4',
+ ],
+ ]),
+ 'created_at' => date('Y-m-d H:i:s'),
+ 'updated_at' => date('Y-m-d H:i:s'),
+ ],
+ [
+ 'id' => '550e8400-e29b-41d4-a716-446655440015',
+ 'name' => 'obsidian-rose',
+ 'display_name' => 'Obsidian Rose',
+ 'description' => 'A sophisticated dark theme blending deep charcoal blacks with rose gold accents. Elegant and bold, this theme is built for those who want style without sacrificing readability.',
+ 'author' => 'ChromaCraft',
+ 'version' => '1.3.0',
+ 'thumbnail_url' => null,
+ 'download_url' => '/themes/obsidian-rose.css',
+ 'price' => 0,
+ 'is_published' => true,
+ 'metadata' => json_encode([
+ 'tags' => ['dark', 'elegant', 'rose', 'premium'],
+ 'colors' => [
+ 'Primary' => '#C9184A',
+ 'Secondary' => '#FF4D6D',
+ 'Background' => '#0A0A0F',
+ 'Surface' => '#1C1C28',
+ 'Text' => '#F1E3E4',
+ 'Accent' => '#B5838D',
+ ],
+ 'vars' => [
+ '--bg' => '#0A0A0F',
+ '--surface' => '#1C1C28',
+ '--surface-strong' => '#242430',
+ '--surface-muted' => '#14141E',
+ '--border' => '#2A2A38',
+ '--line' => '#383848',
+ '--text' => '#F1E3E4',
+ '--text-muted' => '#B5939A',
+ '--text-strong' => '#FAF0F1',
+ '--accent' => '#C9184A',
+ '--accent-text' => '#FFFFFF',
+ '--accent-soft' => '#3D0A1A',
+ '--sidebar-bg' => '#161620',
+ '--sidebar-border' => '#2A2A38',
+ '--sidebar-text' => '#F1E3E4',
+ '--sidebar-text-muted' => '#B5939A',
+ '--input-bg' => '#0A0A0F',
+ '--input-border' => '#2A2A38',
+ '--modal-bg' => '#1C1C28',
+ '--chip' => '#2A2030',
+ '--success' => '#0A2016',
+ ],
+ ]),
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
],
diff --git a/app/Views/theme_store.php b/app/Views/theme_store.php
new file mode 100644
index 0000000..b953bbd
--- /dev/null
+++ b/app/Views/theme_store.php
@@ -0,0 +1,767 @@
+
+
+
+
+
+ Theme Store
+
+
+
+
+
+
+
+
+
✓ = esc($flash_success) ?>
+
+
+
+
⚠ = esc($flash_error) ?>
+
+
+
+
+
+
+
+
+
+
+ = esc($theme['display_name']) ?>
+ v= esc($theme['version']) ?>
+
+
by = esc($theme['author']) ?>
+
= esc($theme['description']) ?>
+
+
+ = esc($tag) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Tags
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/env.example b/env.example
index f359ec2..b12e39c 100644
--- a/env.example
+++ b/env.example
@@ -30,13 +30,13 @@
# DATABASE
#--------------------------------------------------------------------
-# database.default.hostname = localhost
-# database.default.database = ci4
-# database.default.username = root
-# database.default.password = root
-# database.default.DBDriver = MySQLi
+database.default.hostname = localhost
+database.default.database = ci4
+database.default.username = root
+database.default.password = root
+database.default.DBDriver = MySQLi
# database.default.DBPrefix =
-# database.default.port = 3306
+database.default.port = 3306
# If you use MySQLi as tests, first update the values of Config\Database::$tests.
# database.tests.hostname = localhost
diff --git a/public/themes/arctic-frost.css b/public/themes/arctic-frost.css
new file mode 100644
index 0000000..8ebf313
--- /dev/null
+++ b/public/themes/arctic-frost.css
@@ -0,0 +1,16 @@
+/* Arctic Frost Theme — MinimalStudio v3.0.0 */
+:root {
+ --color-primary: #2176AE;
+ --color-secondary: #57C4E5;
+ --color-background: #F8FBFF;
+ --color-surface: #FFFFFF;
+ --color-text: #1C2B3A;
+ --color-accent: #A8DADC;
+}
+
+body { background-color: var(--color-background); color: var(--color-text); font-family: system-ui, sans-serif; }
+a, .link { color: var(--color-primary); }
+.btn-primary { background: var(--color-primary); color: #fff; border: none; border-radius: 6px; padding: 8px 18px; cursor: pointer; }
+.btn-primary:hover { background: var(--color-secondary); }
+.card { background: var(--color-surface); border-radius: 10px; box-shadow: 0 1px 6px rgba(33,118,174,0.08); padding: 20px; border: 1px solid #DDE8F0; }
+.tag { background: var(--color-accent); color: var(--color-text); border-radius: 4px; padding: 2px 8px; font-size: 0.75rem; }
diff --git a/public/themes/forest-grove.css b/public/themes/forest-grove.css
new file mode 100644
index 0000000..a953d12
--- /dev/null
+++ b/public/themes/forest-grove.css
@@ -0,0 +1,16 @@
+/* Forest Grove Theme — NaturePalette v1.0.5 */
+:root {
+ --color-primary: #2D6A4F;
+ --color-secondary: #52B788;
+ --color-background: #F0F7EE;
+ --color-surface: #FFFFFF;
+ --color-text: #1B2E22;
+ --color-accent: #B7E4C7;
+}
+
+body { background-color: var(--color-background); color: var(--color-text); font-family: system-ui, sans-serif; }
+a, .link { color: var(--color-primary); }
+.btn-primary { background: var(--color-primary); color: #fff; border: none; border-radius: 6px; padding: 8px 18px; cursor: pointer; }
+.btn-primary:hover { background: var(--color-secondary); }
+.card { background: var(--color-surface); border-radius: 10px; box-shadow: 0 2px 8px rgba(45,106,79,0.12); padding: 20px; }
+.tag { background: var(--color-accent); color: var(--color-text); border-radius: 4px; padding: 2px 8px; font-size: 0.75rem; }
diff --git a/public/themes/midnight-void.css b/public/themes/midnight-void.css
new file mode 100644
index 0000000..c334f3f
--- /dev/null
+++ b/public/themes/midnight-void.css
@@ -0,0 +1,16 @@
+/* Midnight Void Theme — ThemeForge v2.0.1 */
+:root {
+ --color-primary: #7C3AED;
+ --color-secondary: #A78BFA;
+ --color-background: #0D0D1A;
+ --color-surface: #1A1A2E;
+ --color-text: #E2E8F0;
+ --color-accent: #F472B6;
+}
+
+body { background-color: var(--color-background); color: var(--color-text); font-family: system-ui, sans-serif; }
+a, .link { color: var(--color-secondary); }
+.btn-primary { background: var(--color-primary); color: #fff; border: none; border-radius: 6px; padding: 8px 18px; cursor: pointer; }
+.btn-primary:hover { background: var(--color-accent); }
+.card { background: var(--color-surface); border-radius: 10px; box-shadow: 0 2px 16px rgba(124,58,237,0.25); padding: 20px; }
+.tag { background: var(--color-accent); color: #0D0D1A; border-radius: 4px; padding: 2px 8px; font-size: 0.75rem; }
diff --git a/public/themes/obsidian-rose.css b/public/themes/obsidian-rose.css
new file mode 100644
index 0000000..7bf09da
--- /dev/null
+++ b/public/themes/obsidian-rose.css
@@ -0,0 +1,16 @@
+/* Obsidian Rose Theme — ChromaCraft v1.3.0 */
+:root {
+ --color-primary: #C9184A;
+ --color-secondary: #FF4D6D;
+ --color-background: #0A0A0F;
+ --color-surface: #1C1C28;
+ --color-text: #F1E3E4;
+ --color-accent: #B5838D;
+}
+
+body { background-color: var(--color-background); color: var(--color-text); font-family: system-ui, sans-serif; }
+a, .link { color: var(--color-secondary); }
+.btn-primary { background: var(--color-primary); color: #fff; border: none; border-radius: 6px; padding: 8px 18px; cursor: pointer; }
+.btn-primary:hover { background: var(--color-secondary); }
+.card { background: var(--color-surface); border-radius: 10px; box-shadow: 0 2px 16px rgba(201,24,74,0.2); padding: 20px; }
+.tag { background: var(--color-accent); color: #0A0A0F; border-radius: 4px; padding: 2px 8px; font-size: 0.75rem; }
diff --git a/public/themes/ocean-breeze.css b/public/themes/ocean-breeze.css
new file mode 100644
index 0000000..c79a9da
--- /dev/null
+++ b/public/themes/ocean-breeze.css
@@ -0,0 +1,16 @@
+/* Ocean Breeze Theme — ThemeForge v1.2.0 */
+:root {
+ --color-primary: #0077B6;
+ --color-secondary: #00B4D8;
+ --color-background: #E0F4FF;
+ --color-surface: #FFFFFF;
+ --color-text: #1A2B3C;
+ --color-accent: #48CAE4;
+}
+
+body { background-color: var(--color-background); color: var(--color-text); font-family: system-ui, sans-serif; }
+a, .link { color: var(--color-primary); }
+.btn-primary { background: var(--color-primary); color: #fff; border: none; border-radius: 6px; padding: 8px 18px; cursor: pointer; }
+.btn-primary:hover { background: var(--color-secondary); }
+.card { background: var(--color-surface); border-radius: 10px; box-shadow: 0 2px 8px rgba(0,119,182,0.1); padding: 20px; }
+.tag { background: var(--color-accent); color: var(--color-text); border-radius: 4px; padding: 2px 8px; font-size: 0.75rem; }
diff --git a/public/themes/sunset-ember.css b/public/themes/sunset-ember.css
new file mode 100644
index 0000000..3a9791e
--- /dev/null
+++ b/public/themes/sunset-ember.css
@@ -0,0 +1,16 @@
+/* Sunset Ember Theme — ChromaCraft v1.1.2 */
+:root {
+ --color-primary: #D62828;
+ --color-secondary: #F77F00;
+ --color-background: #FFF5E4;
+ --color-surface: #FFFFFF;
+ --color-text: #2D1B00;
+ --color-accent: #FCBF49;
+}
+
+body { background-color: var(--color-background); color: var(--color-text); font-family: system-ui, sans-serif; }
+a, .link { color: var(--color-primary); }
+.btn-primary { background: var(--color-secondary); color: #fff; border: none; border-radius: 6px; padding: 8px 18px; cursor: pointer; }
+.btn-primary:hover { background: var(--color-primary); }
+.card { background: var(--color-surface); border-radius: 10px; box-shadow: 0 2px 8px rgba(214,40,40,0.1); padding: 20px; }
+.tag { background: var(--color-accent); color: var(--color-text); border-radius: 4px; padding: 2px 8px; font-size: 0.75rem; }
diff --git a/public/todo-preview b/public/todo-preview
new file mode 120000
index 0000000..ad46dac
--- /dev/null
+++ b/public/todo-preview
@@ -0,0 +1 @@
+/home/came/Nextcloud/arch-work/Projects/Todo-App/dist
\ No newline at end of file
diff --git a/writable/.htaccess b/writable/.htaccess
old mode 100644
new mode 100755
diff --git a/writable/cache/index.html b/writable/cache/index.html
old mode 100644
new mode 100755
diff --git a/writable/debugbar/index.html b/writable/debugbar/index.html
old mode 100644
new mode 100755
diff --git a/writable/index.html b/writable/index.html
old mode 100644
new mode 100755
diff --git a/writable/logs/index.html b/writable/logs/index.html
old mode 100644
new mode 100755
diff --git a/writable/session/index.html b/writable/session/index.html
old mode 100644
new mode 100755
diff --git a/writable/uploads/index.html b/writable/uploads/index.html
old mode 100644
new mode 100755