@@ -8,24 +8,45 @@ const apps = [
88 number: " 01" ,
99 name: " QuackNotes" ,
1010 type: " Notes · Apple platforms" ,
11- description: " Fast, file-based notes in plain text, Markdown, and code—with no accounts, proprietary format, or lock-in." ,
11+ description: " A fast, file-based notes app for people who want to own their writing. Work in plain text, Markdown, and code without accounts, proprietary formats, or lock-in." ,
12+ features: [" Plain-text files" , " Markdown & code" , " No account" ],
13+ image: " /assets/apps/quacknotes.webp" ,
14+ imageAlt: " QuackNotes running on macOS with a note and source code open" ,
15+ imageWidth: 1011 ,
16+ imageHeight: 632 ,
17+ siteLabel: " quacknotes.app" ,
1218 url: " https://quacknotes.app" ,
19+ appUrl: undefined ,
1320 color: " yellow" ,
1421 },
1522 {
1623 number: " 02" ,
1724 name: " Clink Clink" ,
1825 type: " Finance · Desktop & web" ,
19- description: " A private expense planner for projecting balances, testing financial scenarios, and understanding loans before life happens." ,
26+ description: " A privacy-first expense planner that projects your balance before life happens. Test financial scenarios, understand loans, and keep every number on your device." ,
27+ features: [" Local data" , " Scenario planning" , " Desktop & web" ],
28+ image: " /assets/apps/clinkclink.png" ,
29+ imageAlt: " Clink Clink showing a financial planning dashboard" ,
30+ imageWidth: 1280 ,
31+ imageHeight: 713 ,
32+ siteLabel: " clinkclink.app" ,
2033 url: " https://clinkclink.app" ,
34+ appUrl: undefined ,
2135 color: " blue" ,
2236 },
2337 {
2438 number: " 03" ,
2539 name: " Punchboard" ,
2640 type: " Time & pay · Open source" ,
27- description: " A local-first calendar for tracking work hours, vacation, sick days, and estimated gross and net pay." ,
41+ description: " A calm, local-first calendar for tracking work hours, vacation, and sick days while estimating gross and net pay—all without sending your data to a server." ,
42+ features: [" Local-first" , " Open source" , " Web & desktop" ],
43+ image: " /assets/apps/punchboard.png" ,
44+ imageAlt: " Punchboard showing work hours, pay estimates, and a monthly calendar" ,
45+ imageWidth: 1280 ,
46+ imageHeight: 800 ,
47+ siteLabel: " quackbyte.dev/punchboard" ,
2848 url: " https://quackbyte.dev/punchboard/" ,
49+ appUrl: " https://quackbyte.dev/punchboard/app/" ,
2950 color: " dark" ,
3051 },
3152];
@@ -46,15 +67,25 @@ const apps = [
4667 </div >
4768 <div class =" app-list" >
4869 { apps .map ((app ) => (
49- <a class :list = { [" app-row" , app .color ]} href = { app .url } target = " _blank" rel = " noreferrer" >
50- <span class = " eyebrow" >{ app .number } </span >
51- <div class = " app-copy" >
52- <strong >{ app .name } </strong >
53- <span class = " app-type" >{ app .type } </span >
54- <p >{ app .description } </p >
70+ <article class :list = { [" app-card" , app .color ]} >
71+ <div class = " app-content" >
72+ <p class = " eyebrow" >{ app .number } · Current product</p >
73+ <h3 >{ app .name } </h3 >
74+ <p class = " app-type" >{ app .type } </p >
75+ <p class = " app-description" >{ app .description } </p >
76+ <ul class = " app-features" aria-label = { ` ${app .name } highlights ` } >
77+ { app .features .map ((feature ) => <li >{ feature } </li >)}
78+ </ul >
79+ <div class = " app-links" >
80+ <a href = { app .url } target = " _blank" rel = " noreferrer" >Visit product <span aria-hidden = " true" >↗</span ></a >
81+ { app .appUrl && <a href = { app .appUrl } target = " _blank" rel = " noreferrer" >Open the app <span aria-hidden = " true" >↗</span ></a >}
82+ </div >
5583 </div >
56- <span class = " app-action" >Visit app <b aria-hidden = " true" >↗</b ></span >
57- </a >
84+ <a class = " app-media" href = { app .url } target = " _blank" rel = " noreferrer" aria-label = { ` Visit ${app .name } ` } >
85+ <div class :list = { [" window-bar" , { dark: app .color === " dark" }]} aria-hidden = " true" ><span ></span ><span ></span ><span ></span ><b >{ app .siteLabel } </b ></div >
86+ <img src = { app .image } alt = { app .imageAlt } width = { app .imageWidth } height = { app .imageHeight } loading = " lazy" />
87+ </a >
88+ </article >
5889 ))}
5990 </div >
6091 </section >
@@ -85,26 +116,42 @@ const apps = [
85116 section + section { margin-top: 10rem; }
86117 .section-heading { display: grid; gap: 1rem; margin-bottom: 3rem; }
87118 .section-heading h2 { font-size: var(--text-4xl); }
88- .app-list { border-top: 1px solid var(--ink); }
89- .app-row { display: grid; grid-template-columns: 2.5rem minmax(0, 1fr); align-items: start; gap: 1rem; padding: 1.8rem 1rem; border-bottom: 1px solid var(--ink); text-decoration: none; transition: padding var(--theme-transition), background var(--theme-transition); }
90- .app-copy { min-width: 0; }
91- .app-copy strong { display: block; font-family: var(--font-brand); font-size: clamp(1.7rem, 4vw, 3.6rem); letter-spacing: -0.04em; line-height: 1; white-space: nowrap; }
92- .app-type { display: block; margin-top: 0.65rem; color: var(--muted); font-family: var(--font-mono); font-size: 0.68rem; text-transform: uppercase; }
93- .app-copy p { max-width: 46rem; margin-top: 1rem; color: var(--muted); font-size: clamp(0.95rem, 1.4vw, 1.08rem); line-height: 1.55; }
94- .app-action { grid-column: 2; justify-self: start; padding-bottom: 0.2rem; border-bottom: 1px solid currentColor; font-family: var(--font-mono); font-size: 0.72rem; white-space: nowrap; }
95- .app-action b { font-size: 1rem; }
96- .app-row:hover { padding-inline: 1.5rem; }
97- .app-row.yellow:hover { background: #ffcf3f; }
98- .app-row.blue:hover { background: #bfe5ff; }
99- .app-row.dark:hover { background: #101827; color: #e7edf5; }
100- .app-row.dark:hover .app-type, .app-row.dark:hover .app-copy p { color: #93a3ba; }
119+ .app-list { display: grid; gap: clamp(3rem, 7vw, 6rem); }
120+ .app-card { display: grid; gap: 2.5rem; overflow: hidden; padding: clamp(1.5rem, 4vw, 3rem); border: 1px solid var(--ink); border-radius: 1.5rem; }
121+ .app-card.yellow { background: #ffcf3f; }
122+ .app-card.blue { background: #bfe5ff; }
123+ .app-card.dark { border-color: #26344b; background: #101827; color: #e7edf5; }
124+ .app-content { min-width: 0; align-self: center; }
125+ .app-content h3 { margin-top: 1.2rem; font-size: clamp(2.6rem, 6vw, 5rem); letter-spacing: -0.055em; line-height: 0.95; white-space: nowrap; }
126+ .app-type { margin-top: 1rem; color: #4b473d; font-family: var(--font-mono); font-size: 0.7rem; text-transform: uppercase; }
127+ .app-description { max-width: 37rem; margin-top: 1.4rem; color: #3e3b32; font-size: clamp(1rem, 1.5vw, 1.15rem); line-height: 1.55; }
128+ .dark .app-type, .dark .app-description { color: #a6b3c7; }
129+ .app-features { display: flex; flex-wrap: wrap; gap: 0.55rem; margin-top: 1.4rem; padding: 0; list-style: none; }
130+ .app-features li { padding: 0.45rem 0.65rem; border: 1px solid currentColor; border-radius: 999px; font-family: var(--font-mono); font-size: 0.62rem; }
131+ .app-links { display: flex; flex-wrap: wrap; gap: 1.5rem; margin-top: 1.8rem; }
132+ .app-links a { padding-bottom: 0.25rem; border-bottom: 1px solid currentColor; font-family: var(--font-mono); font-size: 0.74rem; text-decoration: none; }
133+ .app-media { min-width: 0; align-self: center; overflow: hidden; border: 1px solid var(--ink); border-radius: 0.85rem; background: #222; box-shadow: 12px 14px 0 rgba(21, 23, 20, 0.18); text-decoration: none; transform: rotate(1.2deg); }
134+ .app-card:nth-child(even) .app-media { transform: rotate(-1.2deg); }
135+ .dark .app-media { border-color: #31405a; box-shadow: 12px 14px 0 rgba(0, 0, 0, 0.24); }
136+ .window-bar { display: flex; align-items: center; gap: 0.35rem; height: 2rem; padding-inline: 0.65rem; background: var(--white); color: var(--ink); font-family: var(--font-mono); font-size: 0.62rem; }
137+ .window-bar.dark { border-bottom: 1px solid #28364d; background: #0d1420; color: #93a3ba; }
138+ .window-bar span { width: 0.55rem; aspect-ratio: 1; border: 1px solid currentColor; border-radius: 50%; }
139+ .window-bar b { margin-left: auto; overflow: hidden; font-weight: 400; text-overflow: ellipsis; white-space: nowrap; }
140+ .app-media img { display: block; width: 100%; height: auto; }
101141 .archive-grid { display: grid; gap: 1.5rem; }
102142 .archive-card { display: grid; grid-template-rows: 15rem auto; overflow: hidden; border: 1px solid var(--ink); border-radius: 1rem; background: var(--white); text-decoration: none; transition: transform var(--theme-transition), box-shadow var(--theme-transition); }
103143 .archive-card:hover { transform: translateY(-4px); box-shadow: 7px 7px 0 var(--ink); }
104144 .archive-card img { width: 100%; height: 100%; object-fit: cover; border-bottom: 1px solid var(--ink); filter: saturate(0.75); }
105145 .archive-card div { padding: 1.3rem; }
106146 .archive-card h3 { margin-top: 1rem; font-size: var(--text-xl); letter-spacing: -0.03em; }
107147 .archive-card p { margin-top: 0.7rem; color: var(--muted); }
108- @media (min-width: 42rem) { .app-row { grid-template-columns: 3rem minmax(0, 1fr) auto; align-items: center; padding-block: 2.2rem; } .app-action { grid-column: auto; align-self: center; } }
109- @media (min-width: 52rem) { .section-heading { grid-template-columns: 0.55fr 1.45fr; } .archive-grid { grid-template-columns: repeat(2, 1fr); } }
148+ @media (min-width: 52rem) {
149+ .section-heading { grid-template-columns: 0.55fr 1.45fr; }
150+ .app-card { grid-template-columns: minmax(18rem, 0.78fr) minmax(0, 1.22fr); min-height: 35rem; gap: 3rem; padding-left: clamp(2.5rem, 5vw, 5rem); }
151+ .app-card .app-media { margin-right: -12%; }
152+ .app-card:nth-child(even) { grid-template-columns: minmax(0, 1.22fr) minmax(18rem, 0.78fr); padding-right: clamp(2.5rem, 5vw, 5rem); padding-left: clamp(1.5rem, 4vw, 3rem); }
153+ .app-card:nth-child(even) .app-content { order: 2; }
154+ .app-card:nth-child(even) .app-media { order: 1; margin-right: 0; margin-left: -12%; }
155+ .archive-grid { grid-template-columns: repeat(2, 1fr); }
156+ }
110157</style >
0 commit comments