diff --git a/package.json b/package.json index dd55fd9..66a3635 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "astro-scaffold", "type": "module", - "version": "1.4.0", + "version": "1.5.0", "engines": { "node": ">=22.12.0" }, diff --git a/public/work/motion/hut-palu-48.mp4 b/public/work/motion/hut-palu-48-palette.mp4 similarity index 100% rename from public/work/motion/hut-palu-48.mp4 rename to public/work/motion/hut-palu-48-palette.mp4 diff --git a/public/work/motion/hut-palu-48.webp b/public/work/motion/hut-palu-48-palette.webp similarity index 100% rename from public/work/motion/hut-palu-48.webp rename to public/work/motion/hut-palu-48-palette.webp diff --git a/public/work/portfolio/ayana/promo.mp4 b/public/work/portfolio/ayana/promo.mp4 new file mode 100644 index 0000000..d75a7ea Binary files /dev/null and b/public/work/portfolio/ayana/promo.mp4 differ diff --git a/public/work/portfolio/ayana/promo.webp b/public/work/portfolio/ayana/promo.webp new file mode 100644 index 0000000..16e0958 Binary files /dev/null and b/public/work/portfolio/ayana/promo.webp differ diff --git a/public/work/portfolio/cektrans/launch.mp4 b/public/work/portfolio/cektrans/launch.mp4 new file mode 100644 index 0000000..4faa5a2 Binary files /dev/null and b/public/work/portfolio/cektrans/launch.mp4 differ diff --git a/public/work/portfolio/cektrans/launch.webp b/public/work/portfolio/cektrans/launch.webp new file mode 100644 index 0000000..12e5eb9 Binary files /dev/null and b/public/work/portfolio/cektrans/launch.webp differ diff --git a/public/work/portfolio/lumbung/promo.mp4 b/public/work/portfolio/lumbung/promo.mp4 new file mode 100644 index 0000000..ce346ed Binary files /dev/null and b/public/work/portfolio/lumbung/promo.mp4 differ diff --git a/public/work/portfolio/lumbung/promo.webp b/public/work/portfolio/lumbung/promo.webp new file mode 100644 index 0000000..58b4f75 Binary files /dev/null and b/public/work/portfolio/lumbung/promo.webp differ diff --git a/public/work/portfolio/msp-performance-audit/case-study.mp4 b/public/work/portfolio/msp-performance-audit/case-study.mp4 new file mode 100644 index 0000000..406274b Binary files /dev/null and b/public/work/portfolio/msp-performance-audit/case-study.mp4 differ diff --git a/public/work/portfolio/msp-performance-audit/case-study.webp b/public/work/portfolio/msp-performance-audit/case-study.webp new file mode 100644 index 0000000..2122bed Binary files /dev/null and b/public/work/portfolio/msp-performance-audit/case-study.webp differ diff --git a/src/components/cards/CaseStudyCard.astro b/src/components/cards/CaseStudyCard.astro index 2fd7ad0..a3cf808 100644 --- a/src/components/cards/CaseStudyCard.astro +++ b/src/components/cards/CaseStudyCard.astro @@ -10,6 +10,7 @@ import { renderInlineMd } from '../../lib/inline-md' import { clientLabel } from '../../lib/portfolio-screens' import Mock from '../mocks/Mock.astro' import { hasMock } from '../mocks' +import { hasVideo } from '../../data/portfolio-videos' interface Props { title: string @@ -44,6 +45,7 @@ const bucketLabel = { }[bucket] const clientText = clientLabel(client, en ? 'en' : 'id') const mock = slug ? hasMock(slug) : false +const video = slug ? hasVideo(slug) : false const num = index ? String(index).padStart(2, '0') : '' --- @@ -69,6 +71,7 @@ const num = index ? String(index).padStart(2, '0') : ''

{year}

{bucketLabel}

+ {video &&

Video

}
@@ -88,7 +91,7 @@ const num = index ? String(index).padStart(2, '0') : '' {mock ? : coverSrc && {coverAlt}}
-

{bucketLabel} / {clientText} / {year}

+

{bucketLabel} / {clientText} / {year}{video ? ' / Video' : ''}

{title}

diff --git a/src/components/mocks/Lumbung.astro b/src/components/mocks/Lumbung.astro new file mode 100644 index 0000000..1d4225f --- /dev/null +++ b/src/components/mocks/Lumbung.astro @@ -0,0 +1,96 @@ +--- +/** + * Port of the Lumbung home ledger (shared workspace) from the Flutter app. + * Source: ~/Documents/Projects/Kirana app (HomeLedgerHeader, KPeriodSummaryBar, KLedgerRow, HomeLedgerDaySeparator, + * KOfflineBanner, ShellBottomNavBar + ShellFab), via the source-traced rebuild in + * education_content_remotion/src/compositions/lumbung-promo/ui (Screen.tsx + screen.css.ts). Cross-checked with the live + * mock on https://lumbung.banuacoder.com ("Rumah Budi & Sinta": Income Rp12.400.000, Spending Rp6.553.000). + * Tokens (lumbung_ui_kit): bg #FCFCFB, fg #1E293B, fg-subtle #475569, outline-subtle #E2E8F0, primary #146B5C, + * income #0B7A57, expense #E02424, harvest #E9A93B, member accents #5A6AC0 / #0891B2. + * Layout: design screen 384 wide (dp map 1:1, calc(1 * var(--u)) = 1dp at 384px), status 36, header 12/16/8 padding, ledger row 68, nav 66, FAB 56. + * Icons: Harvest set from app/packages/lumbung_asset. People, rows and amounts are sample data (personas, not customers). + * Font: the app uses Manrope 800 for amounts and titles; this site does not load Manrope, so the site sans is used. + */ +interface Props { locale?: 'id' | 'en' } +const en = Astro.props.locale === 'en' +const t = en + ? { ws: 'Rumah Budi & Sinta', list: 'List', cal: 'Calendar', inc: 'Income', exp: 'Spending', tot: 'Total', today: 'Today · 16 Jul', yday: 'Yesterday · 15 Jul', off: 'Offline · changes saved locally, will sync.', nav: ['Home', 'Insights', 'Money', 'More'], mon: 'July 2026', food: 'Food', bills: 'Bills' } + : { ws: 'Rumah Budi & Sinta', list: 'Daftar', cal: 'Kalender', inc: 'Pemasukan', exp: 'Pengeluaran', tot: 'Total', today: 'Hari ini · 16 Jul', yday: 'Kemarin · 15 Jul', off: 'Offline · perubahan tersimpan lokal, akan disinkronkan.', nav: ['Home', 'Wawasan', 'Uang', 'Lainnya'], mon: 'Juli 2026', food: 'Belanja', bills: 'Tagihan' } +const I: Record = {"arrows/chevron-down":"","navigation/home_active":"","navigation/insights":"","navigation/money":"","navigation/more":"","navigation/add":"","finance/groceries-cart":"","finance/utilities-bolt":"","filter":"","left":"","right":"","cloudOff":""} +const B = ['#5A6AC0', 'B'], S = ['#0891B2', 'S'] +const rows = [ + { a: 'Rp 186.000', t: 'Superindo', s: 'Sinta · BCA', by: S, c: t.food, col: '#16A34A', tx: '#105E2E', w: '18.42' }, + { a: 'Rp 95.000', t: 'Listrik PLN', s: 'Budi · BCA', by: B, c: t.bills, col: '#0891B2', tx: '#066880', w: '14.30' }, + { sep: t.yday, net: '−Rp 128.400' }, + { a: 'Rp 128.400', t: en ? 'Weekly groceries' : 'Belanja mingguan', s: 'Sinta · GoPay', by: S, c: t.food, col: '#16A34A', tx: '#105E2E', w: '10.05' }, +] +--- +
+
9.41
+
+
+
{t.ws}BS
+
{t.mon}
+
{t.list}{t.cal}
+
+
{t.inc}Rp12.400.000
{t.exp}Rp6.553.000
{t.tot}+Rp5.847.000
+
{t.off}
+
{t.today}−Rp 281.000
+ {rows.map((r) => r.sep ? ( +
{r.sep}{r.net}
+ ) : ( +
{r.a}
{r.t}{r.by![1]}{r.s}
{r.c}{r.w}
+ ))} + +
+
+ diff --git a/src/components/mocks/index.ts b/src/components/mocks/index.ts index b747435..4634646 100644 --- a/src/components/mocks/index.ts +++ b/src/components/mocks/index.ts @@ -10,6 +10,7 @@ import Detexi from './Detexi.astro' import FormGearBuilder from './FormGearBuilder.astro' import FormGearEngine from './FormGearEngine.astro' import Lontara from './Lontara.astro' +import Lumbung from './Lumbung.astro' import MspPerformanceAudit from './MspPerformanceAudit.astro' import Patonro from './Patonro.astro' import PicoSulteng from './PicoSulteng.astro' @@ -33,6 +34,7 @@ export const mocks: Record = { 'form-gear-builder': { component: FormGearBuilder, frame: 'desktop', url: 'formgear builder' }, 'form-gear-engine': { component: FormGearEngine, frame: 'phone' }, lontara: { component: Lontara, frame: 'phone' }, + lumbung: { component: Lumbung, frame: 'phone' }, 'msp-performance-audit': { component: MspPerformanceAudit, frame: 'phone' }, patonro: { component: Patonro, frame: 'phone' }, 'pico-sulteng': { component: PicoSulteng, frame: 'phone' }, diff --git a/src/components/portfolio/CaseStudyDetail.astro b/src/components/portfolio/CaseStudyDetail.astro index c4b766b..9212462 100644 --- a/src/components/portfolio/CaseStudyDetail.astro +++ b/src/components/portfolio/CaseStudyDetail.astro @@ -21,6 +21,8 @@ import CTA from '../sections/CTA.astro' import DeviceFrame from '../interactive/DeviceFrame.astro' import Mock from '../mocks/Mock.astro' import { hasMock } from '../mocks' +import MotionPlayer from '../services/MotionPlayer.astro' +import { portfolioVideos } from '../../data/portfolio-videos' interface Props { entry: CollectionEntry<'portfolio'> @@ -40,12 +42,14 @@ const t = en stack: 'Tech Stack', links: 'Links', linksAria: 'External links', testimonial: 'Testimonial', next: 'Next project', view: 'View project →', all: 'All projects', play: 'Get it on Google Play', playImg: '/badges/get-on-google-play.png', - status: { live: 'Live', maintained: 'Maintained', archived: 'Archived' }, + status: { live: 'Live', maintained: 'Maintained', archived: 'Archived', development: 'In active development' }, ctaH: 'Need something like this?', ctaSub: 'Tell us about your product or system. We reply with scope, timeline and a realistic estimate.', screenAlt: (n: number) => `${data.title} screenshot ${n}`, visit: 'Visit product →', facts: { origin: 'Origin', client: 'Client', year: 'Year', bucket: 'Category', status: 'Status', stack: 'Stack' }, fig: 'Fig.', + video: 'Video', videoSub: 'Watch it in motion. Press play; nothing loads until you do.', + labels: { play: 'Play', pause: 'Pause', mute: 'Mute', unmute: 'Unmute', fullscreen: 'Fullscreen', seek: 'Seek' }, } : { home: 'Beranda', skip: 'Lewati ke konten utama', summary: 'Ringkasan proyek', detail: 'Detail proyek', @@ -54,12 +58,14 @@ const t = en stack: 'Tech Stack', links: 'Tautan', linksAria: 'Tautan eksternal', testimonial: 'Testimoni', next: 'Proyek berikutnya', view: 'Lihat Proyek →', all: 'Semua proyek', play: 'Dapatkan di Google Play', playImg: '/badges/dapatkan-di-google-play.png', - status: { live: 'Live', maintained: 'Dipelihara', archived: 'Diarsipkan' }, + status: { live: 'Live', maintained: 'Dipelihara', archived: 'Diarsipkan', development: 'Sedang dikembangkan' }, ctaH: 'Butuh sistem seperti ini?', ctaSub: 'Ceritakan produk atau sistem Anda. Kami balas dengan cakupan, jadwal, dan estimasi yang realistis.', screenAlt: (n: number) => `Tangkapan layar ${data.title} ${n}`, visit: 'Kunjungi produk →', facts: { origin: 'Asal', client: 'Klien', year: 'Tahun', bucket: 'Kategori', status: 'Status', stack: 'Stack' }, fig: 'Gbr.', + video: 'Video', videoSub: 'Lihat versi bergeraknya. Tekan play; tidak ada yang dimuat sebelum Anda menekannya.', + labels: { play: 'Putar', pause: 'Jeda', mute: 'Bisukan', unmute: 'Nyalakan suara', fullscreen: 'Layar penuh', seek: 'Geser posisi video' }, } const all = (await getCollection('portfolio', ({ data: d }) => !d.draft && d.locale === locale)).sort( @@ -89,6 +95,7 @@ const bucketLabel = { }[data.bucket] const screens = getScreens(data.slug) +const videos = portfolioVideos[data.slug] ?? [] const phones = screens.filter((s) => s.kind === 'phone') const desktops = screens.filter((s) => s.kind === 'desktop') const heroScreen = screens[0] @@ -252,12 +259,39 @@ const factRow = 'grid grid-cols-[6.5rem_1fr] gap-4 py-3 border-b rule' + + {videos.length > 0 && ( +
+ +
+ № 04 / {t.video} + {String(videos.length).padStart(2, '0')} {t.video} +
+
+

{t.video}

+

{t.videoSub}

+
+
+ {videos.map((vd) => ( +
+ +
+

{vd.kind[locale]}

+

{vd.title[locale]}

+
+
+ ))} +
+
+
+ )} + {screens.length > 0 && (
- № 04 / {t.showcase} + № {videos.length > 0 ? '05' : '04'} / {t.showcase} {String(screens.length).padStart(2, '0')} {t.fig}

{t.showcase}

@@ -299,7 +333,7 @@ const factRow = 'grid grid-cols-[6.5rem_1fr] gap-4 py-3 border-b rule'
-

№ 05 / {t.detail}

+

№ {String(4 + (videos.length > 0 ? 1 : 0) + (screens.length > 0 ? 1 : 0)).padStart(2, '0')} / {t.detail}

diff --git a/src/components/services/MotionPlayer.astro b/src/components/services/MotionPlayer.astro new file mode 100644 index 0000000..b895228 --- /dev/null +++ b/src/components/services/MotionPlayer.astro @@ -0,0 +1,131 @@ +--- +/** + * MotionPlayer: branded video player (no native controls). Prospectus language: sharp, hairline, + * ultramarine plate with a cyan progress line. Behaviour is in the script below and works for + * every [data-mp] on the page, including after ClientRouter navigations. + */ +interface Props { + src: string + poster: string + ratio: '9 / 16' | '16 / 9' + title: string + labels: { play: string; pause: string; mute: string; unmute: string; fullscreen: string; seek: string } +} +const { src, poster, ratio, title, labels } = Astro.props +--- + +
+ + + + + + +
+ + + + + +
+
+ + + + diff --git a/src/components/services/MotionShowcase.astro b/src/components/services/MotionShowcase.astro index ac2b8f0..bd438a2 100644 --- a/src/components/services/MotionShowcase.astro +++ b/src/components/services/MotionShowcase.astro @@ -3,13 +3,14 @@ * MotionShowcase: real videos made by Banua Coder, shown on the motion-video service page. * Plain