|
1 | 1 | <!DOCTYPE html> |
2 | 2 | <html lang="en"> |
3 | 3 | <head> |
4 | | - <meta charset="utf-8" /> |
5 | | - <meta name="viewport" content="width=device-width, initial-scale=1" /> |
6 | | - <title>Stephen's Apps</title> |
7 | | - <meta name="description" content="A collection of Stephen B's apps — StikDebug, StikBridge beta, and more." /> |
8 | | - <meta name="theme-color" content="#0E1218"> |
9 | | - <link rel="preconnect" href="https://stikdebug.xyz" crossorigin> |
10 | | - <style> |
11 | | - :root{ |
12 | | - --bg: #0E1218; /* solid site background */ |
13 | | - --panel: #141b22; /* solid cards */ |
14 | | - --panel-2: #10171e; /* alt panel */ |
15 | | - --stroke: rgba(255,255,255,0.08); |
16 | | - --shadow: rgba(0,0,0,0.28); |
17 | | - --tint: #293B45; |
18 | | - --fg: #ffffff; |
19 | | - --fg-muted: rgba(255,255,255,0.72); |
20 | | - --chip-bg: rgba(255,255,255,0.08); |
21 | | - --chip-stroke: rgba(255,255,255,0.18); |
22 | | - --radius: 16px; |
23 | | - --gap: 14px; |
24 | | - --maxw: 1200px; |
25 | | - |
26 | | - /* Responsive type scale */ |
27 | | - --h1: clamp(22px, 4vw, 32px); |
28 | | - --h2: clamp(18px, 2.6vw, 22px); |
29 | | - --body: 16px; |
30 | | - --small: 14px; |
31 | | - } |
32 | | - |
33 | | - html, body { |
34 | | - height: 100%; |
35 | | - margin: 0; |
36 | | - color: var(--fg); |
37 | | - background: var(--bg); /* SOLID */ |
38 | | - font: var(--body)/1.5 ui-rounded, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, Apple Color Emoji, Segoe UI Emoji; |
39 | | - -webkit-text-size-adjust: 100%; |
40 | | - text-rendering: optimizeLegibility; |
41 | | - } |
42 | | - |
43 | | - .page { min-height: 100%; display: grid; grid-template-rows: auto 1fr auto; } |
44 | | - .container { max-width: var(--maxw); margin: 0 auto; padding: 20px 16px; } |
45 | | - @media (min-width: 720px){ .container { padding: 28px 20px; } } |
46 | | - |
47 | | - .card { |
48 | | - position: relative; |
49 | | - border-radius: var(--radius); |
50 | | - background: var(--panel); /* SOLID panels */ |
51 | | - border: 1px solid var(--stroke); |
52 | | - box-shadow: 0 8px 18px var(--shadow); |
53 | | - } |
54 | | - .card.alt { background: var(--panel-2); } |
55 | | - .card.pad { padding: 14px; } |
56 | | - @media (min-width: 720px){ .card.pad { padding: 18px; } } |
57 | | - |
58 | | - .hero { display:flex; gap:16px; align-items:flex-start; flex-wrap: wrap; } |
59 | | - .hero-title { font-size: var(--h1); font-weight: 800; letter-spacing:.2px; margin:0; } |
60 | | - .hero-sub { color: var(--fg-muted); margin: 4px 0 0; font-weight: 600; } |
61 | | - |
62 | | - .tags { display:flex; gap:8px; flex-wrap: wrap; margin-top: 10px; } |
63 | | - .chip { display:inline-flex; align-items:center; gap:6px; padding: 6px 10px; border-radius: 999px; background: var(--chip-bg); border: 1px solid var(--chip-stroke); font-weight: 700; font-size: var(--small); } |
64 | | - .chip.beta { color: #ffd166; } |
65 | | - |
66 | | - .toolbar { display:flex; gap:10px; align-items:center; flex-wrap: wrap; margin-left: auto; } |
67 | | - .btn { |
68 | | - padding: 10px 12px; |
69 | | - border-radius: 12px; |
70 | | - border: 1px solid var(--stroke); |
71 | | - background: var(--panel-2); |
72 | | - cursor: pointer; |
73 | | - color: var(--fg); |
74 | | - font-weight: 700; letter-spacing:.2px; |
75 | | - transition: transform .12s ease, background-color .12s ease, border-color .12s ease; |
76 | | - touch-action: manipulation; |
77 | | - } |
78 | | - .btn:hover { transform: translateY(-1px); } |
79 | | - .btn:focus-visible { outline: 2px solid var(--tint); outline-offset: 2px; } |
80 | | - .btn[aria-pressed="true"] { border-color: var(--tint); box-shadow: 0 0 0 2px color-mix(in oklab, var(--tint), transparent 70%); } |
81 | | - |
82 | | - .grid { display:grid; gap: var(--gap); } |
83 | | - /* 1 col mobile → 2 cols tablet → 3 cols desktop */ |
84 | | - .grid.apps { grid-template-columns: 1fr; } |
85 | | - @media (min-width: 700px){ .grid.apps { grid-template-columns: repeat(2, minmax(0,1fr)); } } |
86 | | - @media (min-width: 1040px){ .grid.apps { grid-template-columns: repeat(3, minmax(0,1fr)); } } |
87 | | - |
88 | | - .app-card { display:flex; flex-direction:column; gap:12px; } |
89 | | - .app-head { display:flex; gap:12px; align-items:center; } |
90 | | - .app-icon { |
91 | | - width:64px; height:64px; border-radius:14px; object-fit: cover; border: 1px solid var(--stroke); |
92 | | - flex: 0 0 auto; |
93 | | - } |
94 | | - .app-title { margin:0; font-size: clamp(16px, 1.8vw, 18px); font-weight: 800; } |
95 | | - .app-sub { margin:2px 0 0; color: var(--fg-muted); font-weight:600; font-size: var(--small); } |
96 | | - |
97 | | - .kv { display:grid; grid-template-columns: 120px 1fr; gap: 6px 12px; } |
98 | | - @media (min-width: 720px){ .kv { grid-template-columns: 130px 1fr; } } |
99 | | - .kv .k { color: var(--fg-muted); } |
100 | | - |
101 | | - .actions { display:flex; gap:8px; flex-wrap: wrap; } |
102 | | - .cta { |
103 | | - display:inline-flex; align-items:center; gap:8px; padding:10px 12px; border-radius: 12px; text-decoration:none; |
104 | | - color: var(--fg); border: 1px solid var(--stroke); background: var(--panel-2); font-weight: 800; |
105 | | - transition: transform .12s ease, background-color .12s ease; |
106 | | - } |
107 | | - .cta:hover { transform: translateY(-1px); } |
108 | | - .cta:focus-visible { outline: 2px solid var(--tint); outline-offset: 2px; } |
109 | | - |
110 | | - /* Screenshot rail: efficient + mobile-friendly */ |
111 | | - .screens { display:grid; grid-auto-flow: column; gap: 10px; overflow-x:auto; padding-bottom:6px; scrollbar-width: thin; } |
112 | | - .screens img { |
113 | | - height: clamp(280px, 60vh, 420px); |
114 | | - width: auto; |
115 | | - border-radius: 12px; |
116 | | - border: 1px solid var(--stroke); |
117 | | - box-shadow: 0 8px 16px var(--shadow); |
118 | | - object-fit: cover; |
119 | | - } |
120 | | - |
121 | | - .section-title { font-size: var(--h2); font-weight: 800; margin: 0 0 6px; } |
122 | | - .section-sub { margin: 0 0 14px; color: var(--fg-muted); font-weight: 600; } |
123 | | - |
124 | | - header, footer { padding: 20px 0; } |
125 | | - footer { color: var(--fg-muted); font-weight:600; } |
126 | | - |
127 | | - .muted { color: var(--fg-muted); } |
128 | | - .hidden { display:none; } |
129 | | - .divider { height:1px; background: var(--stroke); margin: 8px 0; } |
130 | | - |
131 | | - /* Reduce motion for users who prefer it */ |
132 | | - @media (prefers-reduced-motion: reduce) { |
133 | | - .btn, .cta { transition: none; } |
134 | | - } |
135 | | - |
136 | | - /* Improve tap targets on small screens */ |
137 | | - @media (max-width: 420px){ |
138 | | - .btn, .cta { padding: 12px 14px; } |
139 | | - .app-icon { width:56px; height:56px; } |
140 | | - .kv { grid-template-columns: 100px 1fr; } |
141 | | - } |
142 | | - </style> |
| 4 | + <meta charset="UTF-8"> |
| 5 | + <title>Site 2.0 Coming Soon</title> |
143 | 6 | </head> |
144 | 7 | <body> |
145 | | -<div class="page"> |
146 | | - <header class="container"> |
147 | | - <div class="card pad"> |
148 | | - <div class="hero"> |
149 | | - <div style="flex:1 1 auto; min-width:260px;"> |
150 | | - <h1 class="hero-title" id="siteTitle">Stephen's Apps</h1> |
151 | | - <p class="hero-sub" id="siteSubtitle">A collection of apps</p> |
152 | | - <div class="tags"> |
153 | | - <span class="chip" id="siteUrl"></span> |
154 | | - </div> |
155 | | - </div> |
156 | | - <div class="toolbar"> |
157 | | - <button class="btn" id="filterFeatured" aria-pressed="true">Featured</button> |
158 | | - <button class="btn" id="filterAll" aria-pressed="false">All Apps</button> |
159 | | - </div> |
160 | | - </div> |
161 | | - </div> |
162 | | - </header> |
163 | | - |
164 | | - <main class="container" id="main"> |
165 | | - <section id="featuredSection" class="card pad"> |
166 | | - <h2 class="section-title">Featured</h2> |
167 | | - <p class="section-sub">Curated picks from Stephen’s toolkit.</p> |
168 | | - <div id="featuredGrid" class="grid apps" aria-live="polite"></div> |
169 | | - </section> |
170 | | - |
171 | | - <div style="height:18px"></div> |
172 | | - |
173 | | - <section id="allSection" class="card pad hidden"> |
174 | | - <h2 class="section-title">All Apps</h2> |
175 | | - <p class="section-sub">Explore every app in the Stik ecosystem.</p> |
176 | | - <div id="appsGrid" class="grid apps"></div> |
177 | | - </section> |
178 | | - </main> |
179 | | - |
180 | | - <footer class="container"> |
181 | | - <div class="card pad" style="text-align:center"> |
182 | | - <div>© <span id="year"></span> Stephen B</div> |
183 | | - </div> |
184 | | - </footer> |
185 | | -</div> |
186 | | - |
187 | | -<script> |
188 | | -const ENDPOINT = 'https://stikdebug.xyz/apps.json'; |
189 | | -let STATE = null; |
190 | | -const $ = s => document.querySelector(s); |
191 | | - |
192 | | -function fmtBytes(b){ |
193 | | - if(!b||b<=0) return ""; |
194 | | - const u=["B","KB","MB","GB","TB"]; let i=0,v=b; |
195 | | - while(v>=1024 && i<u.length-1){ v/=1024; i++; } |
196 | | - return `${v.toFixed(i?1:0)} ${u[i]}`; |
197 | | -} |
198 | | -function isUpcoming(iso){ if(!iso) return false; return new Date(iso).getTime() > Date.now(); } |
199 | | - |
200 | | -function renderSite(){ |
201 | | - const DATA = STATE; if(!DATA) return; |
202 | | - document.title = DATA.name || "Stephen's Apps"; |
203 | | - $('#siteTitle').textContent = DATA.name || "Stephen's Apps"; |
204 | | - $('#siteSubtitle').textContent = DATA.subtitle || ''; |
205 | | - $('#siteUrl').textContent = (DATA.website||'').replace(/^https?:\/\//,''); |
206 | | - document.documentElement.style.setProperty('--tint', DATA.tintColor || '#293B45'); |
207 | | - $('#year').textContent = new Date().getFullYear(); |
208 | | - |
209 | | - const byId = Object.fromEntries((DATA.apps||[]).map(a => [a.bundleIdentifier, a])); |
210 | | - const feat = document.getElementById('featuredGrid'); feat.innerHTML=''; |
211 | | - (DATA.featuredApps||[]).forEach(id => { const a = byId[id]; if(a) feat.appendChild(renderAppCard(a)); }); |
212 | | - |
213 | | - const all = document.getElementById('appsGrid'); all.innerHTML=''; |
214 | | - (DATA.apps||[]).forEach(a => all.appendChild(renderAppCard(a))); |
215 | | -} |
216 | | - |
217 | | -function renderAppCard(app){ |
218 | | - const v = (app.versions && app.versions[0]) || {}; |
219 | | - const isBeta = !!app.beta || /beta/i.test(v.version||'') || /\.beta/.test(app.bundleIdentifier||''); |
220 | | - const upcoming = isUpcoming(v.date); |
221 | | - |
222 | | - const wrap = document.createElement('article'); |
223 | | - wrap.className = 'card pad app-card'; |
224 | | - wrap.innerHTML = ` |
225 | | - <div class="app-head"> |
226 | | - <img class="app-icon" src="${app.iconURL}" alt="${app.name} icon" loading="lazy" decoding="async" /> |
227 | | - <div style="flex:1 1 auto"> |
228 | | - <h3 class="app-title">${app.name}</h3> |
229 | | - <p class="app-sub">${app.subtitle||''}</p> |
230 | | - <div class="tags"> |
231 | | - ${isBeta?'<span class="chip beta">Beta</span>':''} |
232 | | - ${upcoming?`<span class="chip">Launches ${new Date(v.date).toLocaleDateString('en-US',{month:'short',day:'numeric'})}</span>`:''} |
233 | | - ${app.minOSVersion?`<span class="chip">iOS ${app.minOSVersion}+</span>`:''} |
234 | | - </div> |
235 | | - </div> |
236 | | - </div> |
237 | | -
|
238 | | - <div class="kv" style="margin-top:8px"> |
239 | | - <div class="k">Version</div><div>${v.version||'—'}</div> |
240 | | - <div class="k">Updated</div><div>${v.date?new Date(v.date).toLocaleDateString():'—'}</div> |
241 | | - <div class="k">Build</div><div>${v.buildVersion||'—'}</div> |
242 | | - <div class="k">Size</div><div>${fmtBytes(v.size)||'—'}</div> |
243 | | - </div> |
244 | | - <div class="divider"></div> |
245 | | - <p class="muted" style="margin:0">${app.localizedDescription||''}</p> |
246 | | - <div class="screens" style="margin-top:10px"> |
247 | | - ${(app.screenshots?.iphone||[]) |
248 | | - .map(s=>`<img src="${s.imageURL}" alt="${app.name} screenshot" loading="lazy" decoding="async">`) |
249 | | - .join('')} |
250 | | - </div> |
251 | | - <div class="actions" style="margin-top:12px"> |
252 | | - ${app.name==="StikDebug" |
253 | | - ? `<a class="cta" href="https://apps.apple.com/us/app/stikdebug/id6744045754" rel="noopener">App Store</a>` |
254 | | - : ``} |
255 | | - </div>`; |
256 | | - return wrap; |
257 | | -} |
258 | | - |
259 | | -function setupInteractions(){ |
260 | | - const featBtn=$('#filterFeatured'), allBtn=$('#filterAll'); |
261 | | - const feat=$('#featuredSection'), all=$('#allSection'); |
262 | | - function setMode(m){ |
263 | | - const f = (m==='featured'); |
264 | | - feat.classList.toggle('hidden', !f); |
265 | | - all.classList.toggle('hidden', f); |
266 | | - featBtn.setAttribute('aria-pressed', f?'true':'false'); |
267 | | - allBtn.setAttribute('aria-pressed', f?'false':'true'); |
268 | | - localStorage.setItem('mode', m); |
269 | | - document.getElementById('main').scrollIntoView({behavior:'smooth', block:'start'}); |
270 | | - } |
271 | | - featBtn.onclick = () => setMode('featured'); |
272 | | - allBtn.onclick = () => setMode('all'); |
273 | | - setMode(localStorage.getItem('mode') || 'featured'); |
274 | | -} |
275 | | - |
276 | | -(async function(){ |
277 | | - try{ |
278 | | - const res = await fetch(ENDPOINT, { cache: 'no-cache' }); |
279 | | - if(!res.ok) throw new Error(res.status); |
280 | | - STATE = await res.json(); |
281 | | - renderSite(); |
282 | | - setupInteractions(); |
283 | | - }catch(e){ |
284 | | - console.error(e); |
285 | | - } |
286 | | -})(); |
287 | | -</script> |
| 8 | + <h1>Site 2.0 Coming Soon</h1> |
288 | 9 | </body> |
289 | 10 | </html> |
0 commit comments