# dlstack A hand-built, file-driven personal link index. Drop it on any shared host via SFTP — no build step, no framework, no database. To update the page, edit `data/links.json` and re-upload. Live at . ## Setup The repo ships with `data/links.example.json` (Ada Lovelace placeholder data). The app reads `data/links.json` first, and falls back to the example if there's no real file — so a fresh clone renders immediately. To customize: ```bash cp data/links.example.json data/links.json # edit data/links.json — your real content ``` `data/links.json` is gitignored, so your personal content stays out of the public repo. Upload `data/links.json` to your host alongside the other files when you deploy. ## File layout ``` dlstack/ ├── index.html shell + font preloads + no-flash boot script ├── favicon.svg the dL mark, ink/paper adaptive ├── data/ │ ├── links.example.json committed example — used as fallback │ └── links.json your real content (gitignored) └── assets/ ├── css/ │ ├── styles.css editorial template (default) │ └── swiss.css swiss template (overrides, scoped) └── js/ └── app.js loader + renderers ``` Total payload: ~40 KB across all assets (uncompressed). Gzipped: ~12 KB. ## Deploying Upload the whole directory via SFTP/FTP to the web root of any shared host. That's it — no PHP, no Node, no DB. To update content: edit `data/links.json` locally, upload, done. If you're behind Cloudflare during active development, enable **Caching → Configuration → Development Mode** in the CF dashboard to bypass the edge cache for 3 hours. Otherwise purge edge cache after each asset change. ## `data/links.json` schema ```jsonc { "profile": { … }, // who you are "theme": { … }, // colors + which template "sections": [ … ], // groups of items "social": [ … ], // pill buttons under the hero "footer": { … } // bottom strip } ``` ### `profile` | field | type | description | |-----------|--------|---------------------------------------------------| | `name` | string | Hero name. Last word is set in italic accent. | | `handle` | string | Optional. Shown in the top marker (e.g. `@you`). | | `tagline` | string | Italic subtitle. Words separated by `·` get the accent color. | | `bio` | string | One-paragraph intro under the tagline. | | `location`| string | Currently unused after the live clock was removed; safe to leave or omit. | ### `theme` | field | type | values | description | |------------|--------|---------------------------------|-------------| | `accent` | string | hex color, e.g. `"#E8482C"` | Single sharp accent color used throughout. | | `template` | string | `"editorial"` (default), `"swiss"`, or `"cosmos"` | Visual treatment. See [Templates](#templates). | | `mode` | string | unused; kept for future use | Theme toggle (auto/light/dark) is controlled by the corner pill, not this field. | ### `sections` An ordered array of groups. Each section renders with a numbered masthead (`№ 01 / Sites`) and a grid of items. ```json { "id": "sites", // required, unique slug "label": "Sites", // required, the section title "kicker": "Where I live online", // optional italic tagline "layout": "clients", // optional — see Layouts below "headless": true, // optional — render body only, no head "items": [ … ] // required, array of items } ``` Set `headless: true` to render a section's body without the `№ NN / Label / kicker` header. Tighter top margin too, so the contents visually nest under the previous section. Useful for sliding a testimonials carousel underneath a Clients wall, for example — put the testimonials section directly after the clients section in the `sections` array with `headless: true` and they'll read as one. #### Layouts | `layout` | When to use | Grid behavior | |------------------|----------------------------------------|----------------------------------------| | *omitted* | Default bento mix | 12-column asymmetric grid (link cards span 4/12, projects/youtube span 6/12, featured spans 8/12, portfolio spans 12/12) | | `"clients"` | Logo wall of clients/partners | Auto-flowing square tiles, `minmax(108–124px, 1fr)` | | `"testimonials"` | Quotes from clients / collaborators | Crossfade carousel — one quote at a time, auto-advancing every 7s, with prev/next nav, dot indicators, swipe + ←/→ keys, pause on hover/focus. Reduced-motion users see all quotes stacked. | `layout: "clients"` is also auto-detected if the first item in the section has `type: "client"`. Same goes for `layout: "testimonials"` when the first item is `type: "testimonial"`. ### Items Every item lives in a section's `items` array and has a `type` field. The six available types are `link`, `card`, `youtube`, `client`, `portfolio`, and `testimonial` — documented below. #### Optional `date` (any item type) Every item type supports an optional `date` string. Use it to surface "published", "shipped", "released", or "worked on" timestamps without changing the item's `type`. Accepted formats (all ISO-style, all interpreted as plain strings — no time zones, no parsing surprises): | Input | Rendered as | Use for | |----------------|------------------------|------------------------------------| | `"2026"` | `2026` | Year-only — "this is from 2026" | | `"2026-05"` | `May 2026` | Year + month — soft / recurring | | `"2026-05-14"` | `May 14, 2026` | Full date — talks, launches, posts | Where it appears per type: - **`link`** — small monospaced caption under the host (separated by a `·`). - **`card` (project)** — at the end of the tag row. - **`portfolio`** — under the caption description. - **`youtube`** — inline after the title overlay. - **`client`** — rendered as a small caption under the client title. The raw value is also written to the `