docs: update screenshots to reflect current UI state
Replace old screenshots with new ones showing updated dashboard design and weather widget. Update README to display mobile + desktop side-by-side, regenerate social preview images with new dashboard screenshot. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@@ -14,31 +14,30 @@
|
|||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" width="33%">
|
<td align="center" width="50%">
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/mobile-dark/mobile-dark-dashboard-2.png">
|
<source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/01-mobile-dark.png">
|
||||||
<source media="(prefers-color-scheme: light)" srcset="docs/screenshots/mobile-light/mobile-light-dashboard-2.png">
|
<source media="(prefers-color-scheme: light)" srcset="docs/screenshots/01-mobile-light.png">
|
||||||
<img src="docs/screenshots/mobile-light/mobile-light-dashboard-2.png" alt="Dashboard" width="240">
|
<img src="docs/screenshots/01-mobile-light.png" alt="Mobile Dashboard" width="280">
|
||||||
</picture>
|
</picture>
|
||||||
|
<br>
|
||||||
|
<sub>Mobile Dashboard</sub>
|
||||||
</td>
|
</td>
|
||||||
<td align="center" width="33%">
|
<td align="center" width="50%">
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/mobile-dark/mobile-dark-tasks-2.png">
|
<source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/01-web-dark.png">
|
||||||
<source media="(prefers-color-scheme: light)" srcset="docs/screenshots/mobile-light/mobile-light-tasks-2.png">
|
<source media="(prefers-color-scheme: light)" srcset="docs/screenshots/01-web-light.png">
|
||||||
<img src="docs/screenshots/mobile-light/mobile-light-tasks-2.png" alt="Tasks" width="240">
|
<img src="docs/screenshots/01-web-light.png" alt="Desktop Dashboard" width="480">
|
||||||
</picture>
|
|
||||||
</td>
|
|
||||||
<td align="center" width="33%">
|
|
||||||
<picture>
|
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="docs/screenshots/mobile-dark/mobile-dark-meal.png">
|
|
||||||
<source media="(prefers-color-scheme: light)" srcset="docs/screenshots/mobile-light/mobile-light-meal.png">
|
|
||||||
<img src="docs/screenshots/mobile-light/mobile-light-meal.png" alt="Meals" width="240">
|
|
||||||
</picture>
|
</picture>
|
||||||
|
<br>
|
||||||
|
<sub>Desktop Dashboard</sub>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<p align="center"><sub>Toggle GitHub light/dark mode to see both themes.</sub></p>
|
<p align="center">
|
||||||
|
<sub>Toggle GitHub light/dark mode to see both themes • <a href="https://ulsklyc.github.io/oikos/">View all screenshots</a></sub>
|
||||||
|
</p>
|
||||||
|
|
||||||
Oikos is a self-hosted web app for families who want to organize their everyday life in one place — without cloud accounts, subscriptions, or data leaving the house. It runs as a Docker container on any home server or NAS, is accessible from every device in the household, and can be installed as a PWA on phones and tablets.
|
Oikos is a self-hosted web app for families who want to organize their everyday life in one place — without cloud accounts, subscriptions, or data leaving the house. It runs as a Docker container on any home server or NAS, is accessible from every device in the household, and can be installed as a PWA on phones and tablets.
|
||||||
|
|
||||||
|
|||||||
@@ -651,35 +651,35 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="screenshots-track" role="region" aria-label="Screenshots" tabindex="0">
|
<div class="screenshots-track" role="region" aria-label="Screenshots" tabindex="0">
|
||||||
<div class="screenshot-item reveal">
|
<div class="screenshot-item reveal">
|
||||||
<img class="screenshot-img" data-light="screenshots/mobile-light/mobile-light-dashboard-2.png" data-dark="screenshots/mobile-dark/mobile-dark-dashboard-2.png" src="screenshots/mobile-light/mobile-light-dashboard-2.png" alt="Oikos Dashboard" width="270" height="480" loading="lazy">
|
<img class="screenshot-img" data-light="screenshots/01-mobile-light.png" data-dark="screenshots/01-mobile-dark.png" src="screenshots/01-mobile-light.png" alt="Oikos Dashboard" width="270" height="480" loading="lazy">
|
||||||
<p class="screenshot-label">Dashboard</p>
|
<p class="screenshot-label">Dashboard</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="screenshot-item reveal reveal-delay-1">
|
<div class="screenshot-item reveal reveal-delay-1">
|
||||||
<img class="screenshot-img" data-light="screenshots/mobile-light/mobile-light-tasks-2.png" data-dark="screenshots/mobile-dark/mobile-dark-tasks-2.png" src="screenshots/mobile-light/mobile-light-tasks-2.png" alt="Task Management" width="270" height="480" loading="lazy">
|
<img class="screenshot-img" data-light="screenshots/02-mobile-light.png" data-dark="screenshots/02-mobile-dark.png" src="screenshots/02-mobile-light.png" alt="Task Management" width="270" height="480" loading="lazy">
|
||||||
<p class="screenshot-label" data-i18n="feat_tasks_title">Tasks</p>
|
<p class="screenshot-label" data-i18n="feat_tasks_title">Tasks</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="screenshot-item reveal reveal-delay-2">
|
<div class="screenshot-item reveal reveal-delay-2">
|
||||||
<img class="screenshot-img" data-light="screenshots/mobile-light/mobile-light-meal.png" data-dark="screenshots/mobile-dark/mobile-dark-meal.png" src="screenshots/mobile-light/mobile-light-meal.png" alt="Meal Planning" width="270" height="480" loading="lazy">
|
<img class="screenshot-img" data-light="screenshots/03-mobile-light.png" data-dark="screenshots/03-mobile-dark.png" src="screenshots/03-mobile-light.png" alt="Meal Planning" width="270" height="480" loading="lazy">
|
||||||
<p class="screenshot-label" data-i18n="feat_meals_title">Meals</p>
|
<p class="screenshot-label" data-i18n="feat_meals_title">Meals</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="screenshot-item reveal reveal-delay-3">
|
<div class="screenshot-item reveal reveal-delay-3">
|
||||||
<img class="screenshot-img" data-light="screenshots/mobile-light/mobile-light-shopping.png" data-dark="screenshots/mobile-dark/mobile-dark-shopping.png" src="screenshots/mobile-light/mobile-light-shopping.png" alt="Shopping Lists" width="270" height="480" loading="lazy">
|
<img class="screenshot-img" data-light="screenshots/04-mobile-light.png" data-dark="screenshots/04-mobile-dark.png" src="screenshots/04-mobile-light.png" alt="Shopping Lists" width="270" height="480" loading="lazy">
|
||||||
<p class="screenshot-label" data-i18n="feat_shopping_title">Shopping</p>
|
<p class="screenshot-label" data-i18n="feat_shopping_title">Shopping</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="screenshot-item reveal">
|
<div class="screenshot-item reveal">
|
||||||
<img class="screenshot-img" data-light="screenshots/mobile-light/mobile-light-calendar.png" data-dark="screenshots/mobile-dark/mobile-dark-calendar.png" src="screenshots/mobile-light/mobile-light-calendar.png" alt="Calendar" width="270" height="480" loading="lazy">
|
<img class="screenshot-img" data-light="screenshots/05-mobile-light.png" data-dark="screenshots/05-mobile-dark.png" src="screenshots/05-mobile-light.png" alt="Calendar" width="270" height="480" loading="lazy">
|
||||||
<p class="screenshot-label" data-i18n="feat_calendar_title">Calendar</p>
|
<p class="screenshot-label" data-i18n="feat_calendar_title">Calendar</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="screenshot-item reveal reveal-delay-1">
|
<div class="screenshot-item reveal reveal-delay-1">
|
||||||
<img class="screenshot-img" data-light="screenshots/mobile-light/mobile-light-budget-2.png" data-dark="screenshots/mobile-dark/mobile-dark-budget-2.png" src="screenshots/mobile-light/mobile-light-budget-2.png" alt="Budget Tracking" width="270" height="480" loading="lazy">
|
<img class="screenshot-img" data-light="screenshots/06-mobile-light.png" data-dark="screenshots/06-mobile-dark.png" src="screenshots/06-mobile-light.png" alt="Budget Tracking" width="270" height="480" loading="lazy">
|
||||||
<p class="screenshot-label" data-i18n="feat_budget_title">Budget</p>
|
<p class="screenshot-label" data-i18n="feat_budget_title">Budget</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="screenshot-item reveal reveal-delay-2">
|
<div class="screenshot-item reveal reveal-delay-2">
|
||||||
<img class="screenshot-img" data-light="screenshots/mobile-light/mobile-light-notes-2.png" data-dark="screenshots/mobile-dark/mobile-dark-notes-2.png" src="screenshots/mobile-light/mobile-light-notes-2.png" alt="Notes" width="270" height="480" loading="lazy">
|
<img class="screenshot-img" data-light="screenshots/07-mobile-light.png" data-dark="screenshots/07-mobile-dark.png" src="screenshots/07-mobile-light.png" alt="Notes" width="270" height="480" loading="lazy">
|
||||||
<p class="screenshot-label" data-i18n="feat_notes_title">Notes</p>
|
<p class="screenshot-label" data-i18n="feat_notes_title">Notes</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="screenshot-item reveal reveal-delay-3">
|
<div class="screenshot-item reveal reveal-delay-3">
|
||||||
<img class="screenshot-img" data-light="screenshots/mobile-light/mobile-light-contacts-2.png" data-dark="screenshots/mobile-dark/mobile-dark-contacts-2.png" src="screenshots/mobile-light/mobile-light-contacts-2.png" alt="Contacts" width="270" height="480" loading="lazy">
|
<img class="screenshot-img" data-light="screenshots/08-mobile-light.png" data-dark="screenshots/08-mobile-dark.png" src="screenshots/08-mobile-light.png" alt="Contacts" width="270" height="480" loading="lazy">
|
||||||
<p class="screenshot-label" data-i18n="feat_contacts_title">Contacts</p>
|
<p class="screenshot-label" data-i18n="feat_contacts_title">Contacts</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 344 KiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 940 KiB |
|
After Width: | Height: | Size: 940 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 971 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 954 KiB |
|
After Width: | Height: | Size: 946 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 992 KiB |
|
After Width: | Height: | Size: 987 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1008 KiB |
|
After Width: | Height: | Size: 1007 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1006 KiB |
|
After Width: | Height: | Size: 1001 KiB |
|
Before Width: | Height: | Size: 442 KiB |
|
Before Width: | Height: | Size: 379 KiB |
|
Before Width: | Height: | Size: 558 KiB |
|
Before Width: | Height: | Size: 582 KiB |
|
Before Width: | Height: | Size: 415 KiB |
|
Before Width: | Height: | Size: 540 KiB |
|
Before Width: | Height: | Size: 371 KiB |
|
Before Width: | Height: | Size: 326 KiB |
|
Before Width: | Height: | Size: 479 KiB |
|
Before Width: | Height: | Size: 449 KiB |
|
Before Width: | Height: | Size: 383 KiB |
|
Before Width: | Height: | Size: 559 KiB |
|
Before Width: | Height: | Size: 554 KiB |
|
Before Width: | Height: | Size: 434 KiB |
|
Before Width: | Height: | Size: 544 KiB |
|
Before Width: | Height: | Size: 381 KiB |
|
Before Width: | Height: | Size: 334 KiB |
|
Before Width: | Height: | Size: 499 KiB |
|
Before Width: | Height: | Size: 368 KiB |
|
Before Width: | Height: | Size: 347 KiB |
|
Before Width: | Height: | Size: 450 KiB |
|
Before Width: | Height: | Size: 583 KiB |
|
Before Width: | Height: | Size: 379 KiB |
|
Before Width: | Height: | Size: 507 KiB |
|
Before Width: | Height: | Size: 308 KiB |
|
Before Width: | Height: | Size: 276 KiB |
|
Before Width: | Height: | Size: 380 KiB |
|
Before Width: | Height: | Size: 367 KiB |
|
Before Width: | Height: | Size: 345 KiB |
|
Before Width: | Height: | Size: 447 KiB |
|
Before Width: | Height: | Size: 557 KiB |
|
Before Width: | Height: | Size: 393 KiB |
|
Before Width: | Height: | Size: 506 KiB |
|
Before Width: | Height: | Size: 311 KiB |
|
Before Width: | Height: | Size: 277 KiB |
|
Before Width: | Height: | Size: 391 KiB |
|
Before Width: | Height: | Size: 348 KiB After Width: | Height: | Size: 354 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 381 KiB |
@@ -0,0 +1,120 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
import sharp from 'sharp';
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
|
||||||
|
const SCREENSHOT = 'docs/screenshots/01-web-light.png';
|
||||||
|
const BG_COLOR = '#1A1A28';
|
||||||
|
|
||||||
|
// Social image dimensions
|
||||||
|
const DIMENSIONS = {
|
||||||
|
'og-image.png': { width: 1200, height: 630 },
|
||||||
|
'twitter-image.png': { width: 1200, height: 675 },
|
||||||
|
'social-preview.png': { width: 1280, height: 640 }
|
||||||
|
};
|
||||||
|
|
||||||
|
async function createSocialImage(filename, width, height) {
|
||||||
|
// Load and resize screenshot to fit right side (60% of width)
|
||||||
|
const screenshotWidth = Math.floor(width * 0.55);
|
||||||
|
const screenshotHeight = Math.floor(height * 0.7);
|
||||||
|
|
||||||
|
const screenshot = await sharp(SCREENSHOT)
|
||||||
|
.resize(screenshotWidth, screenshotHeight, {
|
||||||
|
fit: 'contain',
|
||||||
|
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
||||||
|
})
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
// Create SVG with text and layout
|
||||||
|
const svg = `
|
||||||
|
<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!-- Background -->
|
||||||
|
<rect width="${width}" height="${height}" fill="${BG_COLOR}"/>
|
||||||
|
|
||||||
|
<!-- Left side content area -->
|
||||||
|
<g transform="translate(60, ${height / 2 - 120})">
|
||||||
|
<!-- Logo badge -->
|
||||||
|
<rect x="0" y="0" width="160" height="28" rx="4" fill="#0A84FF" opacity="0.15"/>
|
||||||
|
<text x="12" y="19" font-family="system-ui, -apple-system, sans-serif" font-size="12" font-weight="600" fill="#0A84FF" letter-spacing="0.5">
|
||||||
|
SELF-HOSTED · OPEN SOURCE
|
||||||
|
</text>
|
||||||
|
|
||||||
|
<!-- Title -->
|
||||||
|
<text x="0" y="80" font-family="system-ui, -apple-system, sans-serif" font-size="64" font-weight="700" fill="#FFFFFF">
|
||||||
|
Oikos
|
||||||
|
</text>
|
||||||
|
|
||||||
|
<!-- Description -->
|
||||||
|
<text x="0" y="130" font-family="system-ui, -apple-system, sans-serif" font-size="18" fill="#B0B0B8" font-weight="400">
|
||||||
|
<tspan x="0" dy="0">The family planner that respects your</tspan>
|
||||||
|
<tspan x="0" dy="28">privacy. Tasks, calendars, shopping, meals,</tspan>
|
||||||
|
<tspan x="0" dy="28">budget — on your own server.</tspan>
|
||||||
|
</text>
|
||||||
|
|
||||||
|
<!-- Feature badges -->
|
||||||
|
<g transform="translate(0, 240)">
|
||||||
|
<g>
|
||||||
|
<rect x="0" y="0" width="90" height="32" rx="6" fill="#2A2A38"/>
|
||||||
|
<text x="12" y="21" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500" fill="#8E8E93">✓ Tasks</text>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(100, 0)">
|
||||||
|
<rect x="0" y="0" width="110" height="32" rx="6" fill="#2A2A38"/>
|
||||||
|
<text x="12" y="21" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500" fill="#8E8E93">📅 Calendar</text>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(220, 0)">
|
||||||
|
<rect x="0" y="0" width="110" height="32" rx="6" fill="#2A2A38"/>
|
||||||
|
<text x="12" y="21" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500" fill="#8E8E93">🛒 Shopping</text>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(340, 0)">
|
||||||
|
<rect x="0" y="0" width="90" height="32" rx="6" fill="#2A2A38"/>
|
||||||
|
<text x="12" y="21" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="500" fill="#8E8E93">🍽 Meals</text>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Position screenshot on the right side
|
||||||
|
const screenshotX = width - screenshotWidth - 40;
|
||||||
|
const screenshotY = Math.floor((height - screenshotHeight) / 2);
|
||||||
|
|
||||||
|
// Composite everything together
|
||||||
|
const image = await sharp({
|
||||||
|
create: {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
channels: 4,
|
||||||
|
background: BG_COLOR
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.composite([
|
||||||
|
{
|
||||||
|
input: Buffer.from(svg),
|
||||||
|
top: 0,
|
||||||
|
left: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: screenshot,
|
||||||
|
top: screenshotY,
|
||||||
|
left: screenshotX
|
||||||
|
}
|
||||||
|
])
|
||||||
|
.png()
|
||||||
|
.toFile(`docs/${filename}`);
|
||||||
|
|
||||||
|
console.log(`✓ Created docs/${filename} (${width}x${height})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('Generating social preview images...\n');
|
||||||
|
|
||||||
|
for (const [filename, { width, height }] of Object.entries(DIMENSIONS)) {
|
||||||
|
await createSocialImage(filename, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n✓ All social images generated successfully!');
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(err => {
|
||||||
|
console.error('Error:', err.message);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||