diff --git a/ansible/deploy.yml b/ansible/deploy.yml index 19b848b..9b4626a 100644 --- a/ansible/deploy.yml +++ b/ansible/deploy.yml @@ -28,13 +28,54 @@ owner: "{{ app_user }}" group: "{{ app_user }}" - - name: Copy binary to target location - copy: - src: ../target/sepiatones_xyz - dest: "{{ app_home }}/sepiatones_xyz" + - name: Install Bun + shell: curl -fsSL https://bun.sh/install | bash + args: + creates: /home/{{ app_user }}/.bun/bin/bun + become_user: "{{ app_user }}" + + - name: Create source directory + file: + path: "{{ app_home }}/src" + state: directory owner: "{{ app_user }}" group: "{{ app_user }}" - mode: "0755" + + - name: Copy package.json + copy: + src: ../package.json + dest: "{{ app_home }}/package.json" + owner: "{{ app_user }}" + group: "{{ app_user }}" + + - name: Copy bun.lockb + copy: + src: ../bun.lockb + dest: "{{ app_home }}/bun.lockb" + owner: "{{ app_user }}" + group: "{{ app_user }}" + + - name: Copy source files + synchronize: + src: ../src/ + dest: "{{ app_home }}/src/" + recursive: yes + owner: no + group: no + archive: yes + delete: yes + + - name: Install dependencies + shell: /home/{{ app_user }}/.bun/bin/bun install --frozen-lockfile + args: + chdir: "{{ app_home }}" + become_user: "{{ app_user }}" + + - name: Build binary + shell: /home/{{ app_user }}/.bun/bin/bun build --compile --minify --target bun --outfile ./sepiatones_xyz ./src/index.tsx + args: + chdir: "{{ app_home }}" + become_user: "{{ app_user }}" - name: Copy data directory synchronize: diff --git a/ansible/hosts.ini b/ansible/hosts.ini index 2a3e189..0270c5a 100644 --- a/ansible/hosts.ini +++ b/ansible/hosts.ini @@ -1,3 +1,3 @@ # It is expected that you have sepiatonesxyz defined in your .ssh/config [production] -sepiatonesxyz ansible_host=sepiatonesxyz ansible_python_interpreter=/usr/bin/python3.11 +sepiatonesxyz ansible_host=sepiatonesxyz ansible_python_interpreter=auto diff --git a/bun.lockb b/bun.lockb old mode 100644 new mode 100755 index 8cd5f52..65b9760 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/data/public/dreccan-mono/dreccan-mono.css b/data/public/dreccan-mono/dreccan-mono.css new file mode 100644 index 0000000..561a425 --- /dev/null +++ b/data/public/dreccan-mono/dreccan-mono.css @@ -0,0 +1,699 @@ +/* ============================================================ + DENSE — a classless CSS sheet + Flat, squared, information-dense, general-purpose. + + Backbone: ress / resset v5.0.2 (inline below, MIT) + Font: Ioskeley Mono + Aesthetic: U.S. Graphics Co. (usgraphics.com) + the-monospace-web + + Swap the entire color scheme by editing the two :root token + blocks below (light / dark). Everything is driven by them. + ============================================================ */ + +/* ------- fonts ------------------------------------------------- */ + +@font-face { + font-family: "Ioskeley Mono"; + src: url("fonts/IoskeleyMono-Regular.woff2") format("woff2"); + font-weight: 400; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: "Ioskeley Mono"; + src: url("fonts/IoskeleyMono-Italic.woff2") format("woff2"); + font-weight: 400; + font-style: italic; + font-display: swap; +} +@font-face { + font-family: "Ioskeley Mono"; + src: url("fonts/IoskeleyMono-SemiBold.woff2") format("woff2"); + font-weight: 600; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: "Ioskeley Mono"; + src: url("fonts/IoskeleyMono-Bold.woff2") format("woff2"); + font-weight: 700; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: "Ioskeley Mono"; + src: url("fonts/IoskeleyMono-BoldItalic.woff2") format("woff2"); + font-weight: 700; + font-style: italic; + font-display: swap; +} +@font-face { + font-family: "Ioskeley Mono"; + src: url("fonts/IoskeleyMono-Black.woff2") format("woff2"); + font-weight: 900; + font-style: normal; + font-display: swap; +} + +/* ------- design tokens (LIGHT theme, default) ---------------- + EDIT THIS BLOCK (or the dark block below) to re-skin the sheet. + ------------------------------------------------------------ */ +:root { + /* base palette */ + --bg: #f4f4ef; /* page background */ + --bg-alt: #e9e9e1; /* raised / inset panels */ + --fg: #16160f; /* main text */ + --fg-muted: #5c5c52; /* secondary text */ + --border: #16160f; /* hard rules & outlines */ + --border-soft: #a6a69a; /* faint inner rules */ + + /* accent (used sparingly: focus, selection, marks) */ + --accent: #0047ff; + --accent-fg: #ffffff; + + /* type */ + --font-family: "Ioskeley Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace; + --font-weight-body: 400; + --font-weight-strong: 700; + --font-weight-heading: 700; + --line-height: 1.45; + --scale: 1rem; /* base font size */ + --measure: 92ch; /* page column width */ + --sides: 4ch; /* horizontal page padding */ + + /* spacing (1lh = one line of leaded text) */ + --space: 0.5lh; + --space-lg: 1lh; + + --border-thickness: 2px; + --border-heavy: var(--border-thickness); + --radius: 0; /* squared by design */ + + font-family: var(--font-family); + font-weight: var(--font-weight-body); + font-style: normal; + font-size: var(--scale); + line-height: var(--line-height); + font-variant-numeric: tabular-nums lining-nums; + font-feature-settings: "liga" 1, "calt" 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + color: var(--fg); + background-color: var(--bg); +} + +/* ------- design tokens (DARK theme) -------------------------- + Auto-applied via prefers-color-scheme, or force it by putting + data-theme="dark" / data-theme="light" on the element. + ------------------------------------------------------------ */ +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) { + --bg: #101010; + --bg-alt: #1b1b1b; + --fg: #ecece5; + --fg-muted: #8c8c82; + --border: #c6c6bb; + --border-soft: #3d3d37; + --accent: #7aa2ff; + --accent-fg: #0a0a12; + } +} +:root[data-theme="dark"] { + --bg: #101010; + --bg-alt: #1b1b1b; + --fg: #ecece5; + --fg-muted: #8c8c82; + --border: #c6c6bb; + --border-soft: #3d3d37; + --accent: #7aa2ff; + --accent-fg: #0a0a12; +} + +/* ============================================================ + BACKBONE — ress / resset v5.0.2 (MIT, inline) + ============================================================ */ + +/* resset.dev • v5.0.2 */ + +/* ---- Global selectors ---- */ +html { + box-sizing: border-box; + -webkit-text-size-adjust: 100%; + word-break: normal; + -moz-tab-size: 4; + tab-size: 4; +} +*, +::before, +::after { + background-repeat: no-repeat; + box-sizing: inherit; +} +::before, +::after { + text-decoration: inherit; + vertical-align: inherit; +} +* { + padding: 0; + margin: 0; +} + +/* ---- General elements ---- */ +hr { overflow: visible; height: 0; color: inherit; } +details, main { display: block; } +summary { display: list-item; } +small { font-size: 80%; } +[hidden] { display: none; } +abbr[title] { + border-bottom: none; + text-decoration: underline; + text-decoration: underline dotted; +} +a { background-color: transparent; } +a:active, a:hover { outline-width: 0; } +code, kbd, pre, samp { font-family: monospace, monospace; } +pre { font-size: 1em; } +b, strong { font-weight: bolder; } +sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } +sub { bottom: -0.25em; } +sup { top: -0.5em; } +table { border-color: inherit; text-indent: 0; } +iframe { border-style: none; } + +/* ---- Forms ---- */ +input { border-radius: 0; } +[type='number']::-webkit-inner-spin-button, [type='number']::-webkit-outer-spin-button { height: auto; } +[type='search'] { -webkit-appearance: textfield; outline-offset: -2px; } +[type='search']::-webkit-search-decoration { -webkit-appearance: none; } +textarea { overflow: auto; resize: vertical; } +button, input, optgroup, select, textarea { font: inherit; } +optgroup { font-weight: bold; } +button { overflow: visible; } +button, select { text-transform: none; } +button, [type='button'], [type='reset'], [type='submit'], [role='button'] { cursor: pointer; } +button::-moz-focus-inner, [type='button']::-moz-focus-inner, +[type='reset']::-moz-focus-inner, [type='submit']::-moz-focus-inner { border-style: none; padding: 0; } +button:-moz-focusring, [type='button']::-moz-focus-inner, +[type='reset']::-moz-focus-inner, [type='submit']::-moz-focus-inner { outline: 1px dotted ButtonText; } +button, html [type='button'], [type='reset'], [type='submit'] { -webkit-appearance: button; } +button, input, select, textarea { background-color: transparent; border-style: none; } +a:focus, button:focus, input:focus, select:focus, textarea:focus { outline-width: 0; } +select { -moz-appearance: none; -webkit-appearance: none; } +select::-ms-expand { display: none; } +select::-ms-value { color: currentColor; } +legend { border: 0; color: inherit; display: table; white-space: normal; } +::-webkit-file-upload-button { -webkit-appearance: button; color: inherit; font: inherit; } +[disabled] { cursor: default; } + +/* ---- Media ---- */ +img { border-style: none; } +progress { vertical-align: baseline; } + +/* ---- Accessibility state ---- */ +[aria-busy='true'] { cursor: progress; } +[aria-controls] { cursor: pointer; } +[aria-disabled='true'] { cursor: default; } + +/* ============================================================ + LAYOUT + ============================================================ */ + +html { + display: flex; + flex-direction: column; + align-items: center; +} + +body { + width: 100%; + min-height: 100vh; + max-width: var(--measure); + padding: var(--space-lg) var(--sides); + margin: 0 auto; + display: flex; + flex-direction: column; +} + +main { flex: 1 1 auto; } + +/* ============================================================ + FLOW UTILITIES + Add one of these single classes to any wrapper to force how + its children are laid out. + - .flow-horizontal : children share a row, wrap when tight. + - .flow-vertical : explicit column flow. Default document + flow is already vertical, so this is a + no-op today; it exists as the symmetric + counterpart and marks intent. + The page column is governed by --measure / --sides; override + those tokens per-page to reclaim horizontal space, e.g. a + dashboard. + ============================================================ */ + +.flow-horizontal, +.flow-vertical { + display: flex; + margin: var(--space) 0; +} + +.flow-vertical { + flex-direction: column; +} + +.flow-horizontal { + flex-flow: row wrap; + align-items: stretch; + gap: var(--space); +} +.flow-horizontal > * { + flex: 1 1 0; + margin-block: 0; +} + +/* ============================================================ + HEADINGS + ============================================================ */ + +h1, h2, h3, h4, h5, h6 { + font-family: inherit; + font-weight: var(--font-weight-heading); + text-transform: uppercase; + letter-spacing: 0.04em; + line-height: 1.2; +} + +h1 { + font-size: clamp(1.7rem, 4.5vw, 2.4rem); + font-weight: 900; + letter-spacing: 0.02em; + margin: var(--space) 0; + padding: 0; + border-bottom: var(--border-heavy) solid var(--border); +} + +h2 { + font-size: 1em; + font-weight: 700; + margin: var(--space-lg) 0; + padding-top: var(--space); + border-top: var(--border-heavy) solid var(--border); +} + +h3 { + font-size: 0.85em; + font-weight: 600; + margin: var(--space) 0; +} + +h4, h5, h6 { + font-size: 1em; + font-weight: 700; + margin: var(--space) 0; +} + +/* ============================================================ + TEXT + ============================================================ */ + +p { margin: var(--space) 0; } + +strong { font-weight: var(--font-weight-strong); } +em { font-style: italic; } +small { font-size: 0.8em; color: var(--fg-muted); } + +a { + color: var(--fg); + text-decoration-thickness: var(--border-thickness); + text-underline-offset: 3px; +} +a:hover { color: var(--accent); } +a:visited { color: var(--fg); } + +mark { + background: var(--accent); + color: var(--accent-fg); + padding: 0 0.2ch; +} + +code { + background: var(--bg-alt); + padding: 0.05em 0.4ch; + font-weight: 500; +} +pre { + border: var(--border-heavy) solid var(--border); + background: var(--bg-alt); + padding: var(--space) 2ch; + margin: var(--space) 0; + overflow-x: auto; + font-size: 0.9em; + line-height: 1.5; +} +pre code { background: none; padding: 0; } + +kbd { + border: var(--border-heavy) solid var(--border); + border-bottom-width: 4px; + background: var(--bg-alt); + padding: 0.05em 0.6ch; + font-size: 0.85em; + font-weight: 500; +} + +blockquote { + border-left: 4px solid var(--border); + padding: 0 var(--space) 0 2ch; + margin: var(--space) 0; +} +blockquote p { margin: var(--space) 0; } +blockquote footer { color: var(--fg-muted); font-size: 0.85em; } + +hr { + border: none; + border-top: 4px double var(--border); + margin: var(--space-lg) 0; +} + +address { font-style: normal; color: var(--fg-muted); margin: var(--space) 0; } + +/* ============================================================ + LISTS + ============================================================ */ + +ul, ol { + margin: var(--space) 0; + padding-left: 3ch; +} +li { margin: 0.15lh 0; } +li::marker { font-weight: 700; } + +ol { + list-style: none; + counter-reset: item; + padding-left: 0; +} +ol > li { + counter-increment: item; +} +ol > li::before { + content: counters(item, ".") ". "; + font-weight: 700; + margin-right: 0.5ch; +} +ol ol, ol ul, ul ol, ul ul { + margin: 0.15lh 0; + padding-left: 3ch; +} + +dl { margin: var(--space) 0; } +dt { font-weight: 700; text-transform: uppercase; font-size: 0.85em; letter-spacing: 0.05em; } +dd { margin: var(--space) 2ch; } + +/* ============================================================ + NAVIGATION + ============================================================ */ + +header { + margin: 0 0 var(--space); +} + +nav { + margin-top: var(--space); + padding-top: var(--space); + border-top: var(--border-heavy) solid var(--border); +} +nav ul { + list-style: none; + display: flex; + flex-wrap: wrap; + gap: 0.4lh 2.5ch; + margin: 0; + padding: 0; +} +nav a { + text-decoration: none; + font-weight: 600; + text-transform: uppercase; + font-size: 0.85em; + letter-spacing: 0.05em; +} +nav a:hover { text-decoration: underline; text-underline-offset: 3px; } + +/* ============================================================ + TABLES + ============================================================ */ + +table { + width: 100%; + border-collapse: collapse; + margin: var(--space) 0; +} +th, td { + border: var(--border-heavy) solid var(--border); + padding: 0.3lh 1ch; + text-align: left; + vertical-align: top; +} +th { + font-weight: 700; + text-transform: uppercase; + font-size: 0.8em; + letter-spacing: 0.06em; +} +caption { + caption-side: bottom; + color: var(--fg-muted); + font-size: 0.8em; + text-align: left; + padding-top: var(--space); +} + +/* ============================================================ + FORMS + ============================================================ */ + +label { + display: block; + font-weight: 600; + text-transform: uppercase; + font-size: 0.8em; + letter-spacing: 0.05em; + margin: var(--space) 0; +} + +fieldset { + border: var(--border-heavy) solid var(--border); + padding: var(--space) 2ch; + margin: var(--space) 0; +} +legend { + background: var(--bg); + padding: 0 1ch; + font-weight: 700; + text-transform: uppercase; + font-size: 0.8em; + letter-spacing: 0.06em; +} + +input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="range"]):not([type="file"]):not([type="color"]), +select, +textarea { + display: block; + width: 100%; + border: var(--border-heavy) solid var(--border); + border-radius: var(--radius); + background: var(--bg); + color: var(--fg); + padding: 0.4lh 1ch; + margin: var(--space) 0; + font: inherit; + -webkit-appearance: none; + appearance: none; +} +:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; +} +input:not([type="checkbox"]):not([type="radio"]):focus, +select:focus, +textarea:focus { + border-color: var(--accent); + outline: none; +} + +select { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23808080'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 1ch center; + background-size: 0.8em; + padding-right: 3ch; +} + +textarea { resize: vertical; min-height: 4lh; } + +::placeholder { color: var(--fg-muted); opacity: 1; } + +button, +input[type="submit"], +input[type="button"], +input[type="reset"], +[role="button"] { + display: inline-block; + border: var(--border-heavy) solid var(--border); + border-radius: var(--radius); + background: var(--bg); + color: var(--fg); + padding: 0.4lh 1.4em; + margin: 0 var(--space) var(--space) 0; + font: inherit; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + line-height: 1; + cursor: pointer; + -webkit-appearance: none; + appearance: none; +} +button:hover, +input[type="submit"]:hover, +input[type="button"]:hover, +input[type="reset"]:hover { + background: var(--fg); + color: var(--bg); +} +button:active, +input[type="submit"]:active, +input[type="button"]:active, +input[type="reset"]:active { + transform: translate(1px, 1px); +} +button:disabled, +input[disabled] { + opacity: 0.5; + cursor: default; +} + +/* flat, squared checkboxes & radios */ +input[type="checkbox"], +input[type="radio"] { + -webkit-appearance: none; + appearance: none; + width: 1.05em; + height: 1.05em; + border: var(--border-heavy) solid var(--border); + border-radius: var(--radius); + background: var(--bg); + display: inline-block; + vertical-align: -0.2em; + margin: 0 0.6ch 0 0; + cursor: pointer; + position: relative; +} +input[type="checkbox"]:checked::before, +input[type="radio"]:checked::before { + content: ""; + position: absolute; + inset: 0; + background: var(--accent); +} +input[type="checkbox"]:focus, +input[type="radio"]:focus { outline: none; border-color: var(--accent); } + +/* ============================================================ + DISCLOSURE (details / summary) + ============================================================ */ + +details { + border: var(--border-heavy) solid var(--border); + margin: var(--space) 0; +} +summary { + cursor: pointer; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + font-size: 0.85em; + padding: 0.7lh 1.5ch; + list-style: none; + display: flex; + align-items: center; + gap: 1ch; +} +summary::-webkit-details-marker { display: none; } +summary::before { + content: "\25B8"; /* ▸ */ + color: var(--accent); +} +details[open] summary::before { content: "\25BE"; } /* ▾ */ +details[open] summary { border-bottom: var(--border-heavy) solid var(--border); } +details :is(p, pre, ul, ol, table, blockquote):last-child { margin-bottom: calc(0.7 * var(--space)); } +details > :not(summary) { padding-left: 1.5ch; padding-right: 1.5ch; } + +/* ============================================================ + MEDIA / MISC + ============================================================ */ + +figure { margin: var(--space) 0; } +figcaption { + color: var(--fg-muted); + font-size: 0.8em; + margin-top: 0.35lh; +} + +img, video, iframe { + display: block; + max-width: 100%; + height: auto; + border: var(--border-heavy) solid var(--border); +} + +progress { + -webkit-appearance: none; + appearance: none; + width: 100%; + height: 1.2em; + border: var(--border-heavy) solid var(--border); + background: var(--bg); + margin: var(--space) 0; +} +progress::-webkit-progress-bar { background: var(--bg); } +progress::-webkit-progress-value { background: var(--accent); } +progress::-moz-progress-bar { background: var(--accent); } + +footer { + margin-top: var(--space-lg); + padding-top: var(--space); + border-top: var(--border-heavy) solid var(--border); + color: var(--fg-muted); + font-size: 0.85em; +} + +::selection { + background: var(--accent); + color: var(--accent-fg); +} + +/* plain, squared scrollbars */ +* { scrollbar-width: thin; scrollbar-color: var(--border-soft) transparent; } +*::-webkit-scrollbar { width: 12px; height: 12px; background: var(--bg); } +*::-webkit-scrollbar-thumb { + border: var(--border-thickness) solid var(--border); + background: var(--border-soft); +} + +/* accessibility: no animations/transforms on demand */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + transform: none !important; + } +} + +/* responsive: tighten the column on small screens */ +@media screen and (max-width: 540px) { + :root { font-size: 0.92rem; } + body { padding: var(--space-lg) 2ch; } +} diff --git a/data/public/dreccan-mono/fonts/IoskeleyMono-Black.woff2 b/data/public/dreccan-mono/fonts/IoskeleyMono-Black.woff2 new file mode 100644 index 0000000..4491d3c Binary files /dev/null and b/data/public/dreccan-mono/fonts/IoskeleyMono-Black.woff2 differ diff --git a/data/public/dreccan-mono/fonts/IoskeleyMono-Bold.woff2 b/data/public/dreccan-mono/fonts/IoskeleyMono-Bold.woff2 new file mode 100644 index 0000000..98140c0 Binary files /dev/null and b/data/public/dreccan-mono/fonts/IoskeleyMono-Bold.woff2 differ diff --git a/data/public/dreccan-mono/fonts/IoskeleyMono-BoldItalic.woff2 b/data/public/dreccan-mono/fonts/IoskeleyMono-BoldItalic.woff2 new file mode 100644 index 0000000..0a48b01 Binary files /dev/null and b/data/public/dreccan-mono/fonts/IoskeleyMono-BoldItalic.woff2 differ diff --git a/data/public/dreccan-mono/fonts/IoskeleyMono-Italic.woff2 b/data/public/dreccan-mono/fonts/IoskeleyMono-Italic.woff2 new file mode 100644 index 0000000..b46abf1 Binary files /dev/null and b/data/public/dreccan-mono/fonts/IoskeleyMono-Italic.woff2 differ diff --git a/data/public/dreccan-mono/fonts/IoskeleyMono-Medium.woff2 b/data/public/dreccan-mono/fonts/IoskeleyMono-Medium.woff2 new file mode 100644 index 0000000..5eee8cb Binary files /dev/null and b/data/public/dreccan-mono/fonts/IoskeleyMono-Medium.woff2 differ diff --git a/data/public/dreccan-mono/fonts/IoskeleyMono-Regular.woff2 b/data/public/dreccan-mono/fonts/IoskeleyMono-Regular.woff2 new file mode 100644 index 0000000..a9351a9 Binary files /dev/null and b/data/public/dreccan-mono/fonts/IoskeleyMono-Regular.woff2 differ diff --git a/data/public/dreccan-mono/fonts/IoskeleyMono-SemiBold.woff2 b/data/public/dreccan-mono/fonts/IoskeleyMono-SemiBold.woff2 new file mode 100644 index 0000000..4f9816b Binary files /dev/null and b/data/public/dreccan-mono/fonts/IoskeleyMono-SemiBold.woff2 differ diff --git a/data/posts/things_ai_agents_should_be_able_to_do.html b/data/unposted_posts/things_ai_agents_should_be_able_to_do.html similarity index 100% rename from data/posts/things_ai_agents_should_be_able_to_do.html rename to data/unposted_posts/things_ai_agents_should_be_able_to_do.html diff --git a/justfile b/justfile index 9c24bd9..531b930 100644 --- a/justfile +++ b/justfile @@ -7,7 +7,7 @@ dev: build: bun build --compile --minify --target bun --outfile ./target/sepiatones_xyz ./src/index.tsx -deploy: build +deploy: ansible-playbook -i ansible/hosts.ini ansible/deploy.yml test: