new sub page paradigm

This commit is contained in:
samantha42
2026-08-28 01:36:11 +02:00
parent 5d3c428112
commit 3d3d87fad9
23 changed files with 896 additions and 252 deletions
+153
View File
@@ -0,0 +1,153 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>42</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400;0,500;1,400&family=DM+Mono:wght@300&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--ink: #1a1a18;
--ink-light: #9a9890;
--paper: #fafaf7;
--accent: #1a1a18;
}
html, body {
height: 100%;
background: var(--paper);
color: var(--ink);
}
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 2rem;
}
.container {
max-width: 480px;
width: 100%;
text-align: center;
}
.number {
font-family: 'DM Mono', monospace;
font-weight: 300;
font-size: clamp(5rem, 20vw, 9rem);
letter-spacing: -0.04em;
color: var(--ink);
line-height: 1;
opacity: 0;
transform: translateY(12px);
animation: rise 1s cubic-bezier(0.16, 1, 0.3, 1) 0.1s forwards;
user-select: none;
}
.divider {
width: 1px;
height: 48px;
background: var(--ink-light);
margin: 2.5rem auto;
opacity: 0;
transform: scaleY(0);
transform-origin: top;
animation: grow 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0.6s forwards;
}
.quote {
font-family: 'EB Garamond', Georgia, serif;
font-size: clamp(1.15rem, 3.5vw, 1.4rem);
font-style: italic;
line-height: 1.6;
color: var(--ink);
letter-spacing: 0.01em;
opacity: 0;
animation: rise 1s cubic-bezier(0.16, 1, 0.3, 1) 0.9s forwards;
}
.attribution {
font-family: 'DM Mono', monospace;
font-weight: 300;
font-size: 0.7rem;
letter-spacing: 0.12em;
color: var(--ink-light);
margin-top: 1.25rem;
text-transform: uppercase;
opacity: 0;
animation: rise 1s cubic-bezier(0.16, 1, 0.3, 1) 1.2s forwards;
}
.secret-hint {
margin-top: 4rem;
opacity: 0;
animation: rise 1s cubic-bezier(0.16, 1, 0.3, 1) 2s forwards;
}
.secret-hint p {
font-family: 'DM Mono', monospace;
font-weight: 300;
font-size: 0.68rem;
letter-spacing: 0.08em;
color: var(--ink-light);
cursor: default;
transition: color 0.4s;
user-select: none;
}
.secret-hint p:hover {
color: var(--ink);
}
.secret-hint a {
font-family: 'DM Mono', monospace;
font-weight: 300;
font-size: 0.68rem;
letter-spacing: 0.08em;
color: transparent;
text-decoration: none;
transition: color 0.6s, letter-spacing 0.4s;
cursor: pointer;
}
.secret-hint:hover a {
color: var(--ink-light);
letter-spacing: 0.14em;
}
.secret-hint a:hover {
color: var(--ink) !important;
text-decoration: underline;
text-underline-offset: 3px;
}
@keyframes rise {
to { opacity: 1; transform: translateY(0) scaleY(1); }
}
@keyframes grow {
to { opacity: 1; transform: scaleY(1); }
}
</style>
</head>
<body>
<div class="container">
<div class="number">42</div>
<div class="divider"></div>
<p class="quote">So long, and thanks for all the fish.</p>
<p class="attribution">Douglas Adams &nbsp;·&nbsp; The Hitchhiker's Guide to the Galaxy</p>
<div class="secret-hint">
<p>shhh. i have a secret to share &mdash; <a href="/secret">follow me</a></p>
</div>
</div>
</body>
</html>
+516
View File
@@ -0,0 +1,516 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>admin · samantha42.xyz</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500&family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet" />
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #fafaf8;
--bg-2: #f1efe8;
--text: #1a1a18;
--muted: #888780;
--border: rgba(0,0,0,0.12);
--accent: #185FA5;
--accent-dim: #E6F1FB;
--success: #0F6E56;
--success-dim: #E1F5EE;
--danger: #A32D2D;
--danger-dim: #FCEBEB;
--warn: #854F0B;
--warn-dim: #FAEEDA;
--ff-mono: 'IBM Plex Mono', monospace;
--ff-sans: 'IBM Plex Sans', sans-serif;
}
body.night {
--bg: #111110;
--bg-2: #1a1a18;
--text: #e8e6df;
--muted: #5F5E5A;
--border: rgba(255,255,255,0.1);
--accent-dim: #042C53;
}
body {
font-family: var(--ff-mono);
background: var(--bg);
color: var(--text);
min-height: 100vh;
font-size: 13px;
}
nav {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 2rem;
height: 48px;
border-bottom: 0.5px solid var(--border);
}
.nav-logo { font-size: 13px; color: var(--text); text-decoration: none; }
.nav-right { display: flex; align-items: center; gap: 1.5rem; }
.nav-right a { font-size: 12px; color: var(--muted); text-decoration: none; }
.nav-right a:hover { color: var(--text); }
.shell { display: grid; grid-template-columns: 200px 1fr; min-height: calc(100vh - 48px); }
aside {
border-right: 0.5px solid var(--border);
padding: 1.5rem 1rem;
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.sidebar-label {
font-size: 10px;
color: var(--muted);
letter-spacing: 2px;
text-transform: uppercase;
margin-bottom: 8px;
}
.sidebar-links { display: flex; flex-direction: column; gap: 2px; }
.sidebar-links a {
font-size: 12px;
color: var(--muted);
text-decoration: none;
padding: 5px 8px;
border-radius: 4px;
transition: background 0.15s, color 0.15s;
}
.sidebar-links a:hover { background: var(--bg-2); color: var(--text); }
.sidebar-links a.active { background: var(--bg-2); color: var(--text); }
main { padding: 2rem; max-width: 900px; }
.page-header { margin-bottom: 2rem; }
.page-title { font-size: 20px; font-weight: 500; color: var(--text); }
.page-sub { font-size: 11px; color: var(--muted); letter-spacing: 1px; }
.stat-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 10px;
margin-bottom: 2rem;
}
.stat-card {
background: var(--bg-2);
border-radius: 6px;
padding: 1rem;
}
.stat-label { font-size: 10px; color: var(--muted); letter-spacing: 1px; text-transform: uppercase; margin-bottom: 6px; }
.stat-value { font-size: 22px; font-weight: 500; color: var(--text); }
.stat-sub { font-size: 10px; color: var(--muted); margin-top: 2px; }
.section-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
padding-bottom: 8px;
border-bottom: 0.5px solid var(--border);
}
.section-head h2 { font-size: 13px; font-weight: 500; color: var(--text); }
.job-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 2rem; }
.job-row {
display: grid;
grid-template-columns: 1fr 120px 100px 80px;
align-items: center;
gap: 12px;
padding: 10px 12px;
border: 0.5px solid var(--border);
border-radius: 6px;
background: var(--bg);
}
.job-name { font-size: 12px; color: var(--text); }
.job-time { font-size: 11px; color: var(--muted); }
.badge {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 10px;
padding: 3px 8px;
border-radius: 4px;
font-family: var(--ff-mono);
letter-spacing: 0.5px;
}
.badge-ok { background: var(--success-dim); color: var(--success); }
.badge-fail { background: var(--danger-dim); color: var(--danger); }
.badge-run { background: var(--warn-dim); color: var(--warn); }
.badge-idle { background: var(--bg-2); color: var(--muted); }
.dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.run-btn {
font-family: var(--ff-mono);
font-size: 11px;
padding: 4px 10px;
border: 0.5px solid var(--border);
border-radius: 4px;
background: transparent;
color: var(--muted);
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.run-btn:hover { background: var(--bg-2); color: var(--text); }
.compose-card {
border: 0.5px solid var(--border);
border-radius: 8px;
overflow: hidden;
background: var(--bg);
margin-bottom: 2rem;
}
.compose-head {
background: #0C447C;
padding: 1rem 1.25rem;
display: flex;
align-items: center;
justify-content: space-between;
}
.compose-head-title { font-size: 12px; color: #B5D4F4; letter-spacing: 1px; text-transform: uppercase; }
.compose-head-sub { font-size: 11px; color: #378ADD; }
.compose-body { padding: 1.25rem; display: flex; flex-direction: column; gap: 1rem; }
.field-label {
font-size: 10px;
color: var(--muted);
letter-spacing: 1px;
text-transform: uppercase;
display: block;
margin-bottom: 5px;
}
.field-input, .field-select, .field-textarea {
width: 100%;
font-family: var(--ff-mono);
font-size: 13px;
padding: 9px 12px;
border: 0.5px solid var(--border);
border-radius: 5px;
background: var(--bg-2);
color: var(--text);
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
}
.field-input:focus, .field-select:focus, .field-textarea:focus {
border-color: #378ADD;
box-shadow: 0 0 0 3px rgba(55,138,221,0.12);
}
.field-textarea { resize: vertical; min-height: 200px; line-height: 1.6; }
.compose-footer {
padding: 1rem 1.25rem;
border-top: 0.5px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.sub-count { font-size: 11px; color: var(--muted); }
.btn-send {
font-family: var(--ff-mono);
font-size: 12px;
padding: 8px 20px;
background: #185FA5;
color: #E6F1FB;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background 0.2s, transform 0.1s;
letter-spacing: 0.5px;
}
.btn-send:hover { background: #0C447C; }
.btn-send:active { transform: scale(0.98); }
.btn-preview {
font-family: var(--ff-mono);
font-size: 12px;
padding: 8px 16px;
background: transparent;
color: var(--muted);
border: 0.5px solid var(--border);
border-radius: 5px;
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.btn-preview:hover { background: var(--bg-2); color: var(--text); }
.send-result {
font-size: 11px;
color: var(--success);
display: none;
margin-top: 4px;
}
.htmx-indicator {
opacity: 0;
font-size: 11px;
color: var(--muted);
transition: opacity 0.2s;
}
.htmx-request .htmx-indicator { opacity: 1; }
.section-gap { margin-bottom: 2.5rem; }
.ticker-row {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border: 0.5px solid var(--border);
border-radius: 6px;
margin-bottom: 6px;
font-size: 12px;
}
.ticker-symbol { font-weight: 500; min-width: 80px; }
.ticker-rating-buy { color: var(--success); }
.ticker-rating-sell { color: var(--danger); }
.ticker-muted { color: var(--muted); flex: 1; }
@media (max-width: 640px) {
.shell { grid-template-columns: 1fr; }
aside { display: none; }
.job-row { grid-template-columns: 1fr 80px; }
.job-time, .run-btn { display: none; }
}
</style>
</head>
<body id="themeRoot">
<nav>
<a class="nav-logo" href="/">samantha_vero_friis</a>
<div class="nav-right">
<span style="font-size:11px;color:var(--muted)">admin portal</span>
<a href="/logout">logout →</a>
</div>
</nav>
<div class="shell">
<aside>
<div>
<p class="sidebar-label">admin</p>
<div class="sidebar-links">
<a href="#overview" class="active">overview</a>
<a href="#jobs">job status</a>
<a href="#newsletter">newsletter</a>
<a href="#research">eq research</a>
</div>
</div>
<div>
<p class="sidebar-label">site</p>
<div class="sidebar-links">
<a href="/" target="_blank">view site ↗</a>
<a href="/git" target="_blank">git repo ↗</a>
</div>
</div>
</aside>
<main>
<div class="page-header" id="overview">
<div class="page-title">admin</div>
<div class="page-sub">samantha42.xyz · control panel</div>
</div>
<div class="stat-grid">
<div class="stat-card">
<div class="stat-label">subscribers</div>
<div class="stat-value" hx-get="/admin/api/stats/subscribers" hx-trigger="load" hx-swap="innerHTML"></div>
<div class="stat-sub">newsletter list</div>
</div>
<div class="stat-card">
<div class="stat-label">emails sent</div>
<div class="stat-value" hx-get="/admin/api/stats/sent" hx-trigger="load" hx-swap="innerHTML"></div>
<div class="stat-sub">all time</div>
</div>
<div class="stat-card">
<div class="stat-label">analyses</div>
<div class="stat-value">2</div>
<div class="stat-sub">published</div>
</div>
<div class="stat-card">
<div class="stat-label">jobs ok</div>
<div class="stat-value" hx-get="/admin/api/stats/jobs" hx-trigger="load" hx-swap="innerHTML"></div>
<div class="stat-sub">last 24h</div>
</div>
</div>
<div class="section-gap" id="jobs">
<div class="section-head">
<h2>// job status</h2>
<button class="run-btn" hx-get="/admin/api/jobs" hx-target="#job-list" hx-swap="innerHTML">refresh</button>
</div>
<div class="job-list" id="job-list"
hx-get="/admin/api/jobs"
hx-trigger="load"
hx-swap="innerHTML">
<div class="job-row">
<span class="job-name">portfolio-engine</span>
<span class="job-time">checking...</span>
<span class="badge badge-idle"><span class="dot"></span> loading</span>
<span></span>
</div>
</div>
</div>
<div class="section-gap" id="newsletter">
<div class="section-head">
<h2>// send newsletter</h2>
<span class="htmx-indicator" id="send-spinner">sending...</span>
</div>
<div class="compose-card">
<div class="compose-head">
<span class="compose-head-title">equity research · newsletter</span>
<span class="compose-head-sub">to all subscribers</span>
</div>
<div class="compose-body">
<div>
<label class="field-label" for="nl-subject">subject</label>
<input class="field-input" type="text" id="nl-subject" name="subject"
placeholder="MSTR: why the premium collapses without a bid" />
</div>
<div>
<label class="field-label" for="nl-ticker">ticker (optional)</label>
<input class="field-input" type="text" id="nl-ticker" name="ticker"
placeholder="MSTR · sell" style="width:200px;" />
</div>
<div>
<label class="field-label" for="nl-body">body</label>
<textarea class="field-textarea" id="nl-body" name="body"
placeholder="// thesis&#10;&#10;The truth I keep coming back to..."></textarea>
</div>
<div>
<label class="field-label" for="nl-pdf">attach pdf (path on server)</label>
<input class="field-input" type="text" id="nl-pdf" name="pdf_path"
placeholder="/static/pdf/mstr.pdf" style="width:300px;" />
</div>
</div>
<div class="compose-footer">
<span class="sub-count" hx-get="/admin/api/stats/subscribers" hx-trigger="load" hx-swap="innerHTML"
style="font-size:11px;color:var(--muted)">— subscribers</span>
<div style="display:flex;gap:8px;align-items:center;">
<button class="btn-preview" onclick="previewEmail()">preview</button>
<button class="btn-send"
hx-post="/admin/api/newsletter/send"
hx-include="#nl-subject,#nl-ticker,#nl-body,#nl-pdf"
hx-target="#send-result"
hx-swap="innerHTML"
hx-indicator="#send-spinner">
send to all →
</button>
</div>
</div>
<div id="send-result" style="padding:0 1.25rem 1rem;font-size:11px;"></div>
</div>
</div>
<div class="section-gap" id="research">
<div class="section-head">
<h2>// equity research</h2>
<a href="#newsletter" style="font-size:11px;color:var(--muted);text-decoration:none;">send as newsletter ↑</a>
</div>
<div class="ticker-row">
<span class="ticker-symbol">MSTR</span>
<span class="ticker-rating-sell">sell</span>
<span class="ticker-muted">Strategy · BTC proxy via debt issuance</span>
<a href="/static/pdf/mstr.pdf" target="_blank" style="font-size:11px;color:var(--muted);text-decoration:none;">pdf ↗</a>
</div>
<div class="ticker-row">
<span class="ticker-symbol">NOVO.B / NVO</span>
<span class="ticker-rating-buy">buy</span>
<span class="ticker-muted">Novo Nordisk · pipeline undervalued</span>
<a href="/static/pdf/novo.pdf" target="_blank" style="font-size:11px;color:var(--muted);text-decoration:none;">pdf ↗</a>
</div>
<div style="margin-top:1.5rem;">
<div class="section-head">
<h2>// add new analysis</h2>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:10px;">
<div>
<label class="field-label">ticker</label>
<input class="field-input" type="text" name="new_ticker" placeholder="AAPL" />
</div>
<div>
<label class="field-label">rating</label>
<select class="field-select" name="new_rating">
<option value="buy">buy</option>
<option value="sell">sell</option>
<option value="hold">hold</option>
</select>
</div>
</div>
<div style="margin-bottom:10px;">
<label class="field-label">summary</label>
<input class="field-input" type="text" name="new_summary" placeholder="one-line thesis" />
</div>
<div style="margin-bottom:10px;">
<label class="field-label">pdf path</label>
<input class="field-input" type="text" name="new_pdf" placeholder="/static/pdf/aapl.pdf" style="width:300px;" />
</div>
<button class="btn-send"
hx-post="/admin/api/research/add"
hx-include="[name='new_ticker'],[name='new_rating'],[name='new_summary'],[name='new_pdf']"
hx-target="#research-result"
hx-swap="innerHTML">
publish →
</button>
<div id="research-result" style="margin-top:8px;font-size:11px;"></div>
</div>
</div>
</main>
</div>
<script>
function previewEmail() {
const subject = document.getElementById('nl-subject').value;
const body = document.getElementById('nl-body').value;
const w = window.open('', '_blank');
w.document.write(`<!DOCTYPE html><html><head><meta charset="UTF-8">
<style>
body{font-family:'IBM Plex Mono',monospace;max-width:600px;margin:40px auto;padding:0 20px;background:#fafaf8;color:#1a1a18;font-size:13px;line-height:1.7;}
h1{font-size:18px;font-weight:500;margin-bottom:4px;}
.sub{font-size:11px;color:#888780;margin-bottom:2rem;}
pre{white-space:pre-wrap;font-family:inherit;}
.footer{margin-top:3rem;padding-top:1rem;border-top:0.5px solid #d3d1c7;font-size:10px;color:#888780;}
</style></head><body>
<h1>${subject || '(no subject)'}</h1>
<div class="sub">samantha42.xyz · equity research</div>
<pre>${body || '(no body)'}</pre>
<div class="footer">samantha42.xyz · me@samantha42.xyz · unsubscribe</div>
</body></html>`);
}
document.querySelectorAll('.sidebar-links a').forEach(a => {
a.addEventListener('click', function() {
document.querySelectorAll('.sidebar-links a').forEach(x => x.classList.remove('active'));
this.classList.add('active');
});
});
</script>
</body>
</html>
+383
View File
@@ -0,0 +1,383 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>The Industry That Ate Itself - Samantha Vero Friis</title>
<link
href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&family=Syne:wght@400;700;800&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="/static/style/styles.css" />
</head>
<body>
<div class="container--narrow">
<header>
<a href="/" class="back">← back</a>
<p class="tag">// opinion</p>
<h1>The Industry<br />That Ate<br /><span>Itself</span></h1>
<div class="byline">
<span>Samantha Vero Friis</span>
<span></span>
<span>Film &amp; Media</span>
</div>
</header>
<article>
<p class="lead">
Cinema is not dying because audiences stopped caring about
film. It is dying because the people running it stopped
making decisions that could produce anything worth caring
about. That is a business problem, not a cultural one - and
it has a clear and traceable cause.
</p>
<h2>How You Optimize a Creative Industry to Death</h2>
<p>
Every major studio is ultimately answerable to capital. That
is not unusual - most industries are. What is unusual about
film is that capital and creative quality are in unusually
direct tension. A film that takes a genuine risk might return
ten times its budget or nothing. A sequel to an established
property will almost certainly return two or three times its
budget, reliably, with predictable variance. For a public
company managing quarterly earnings, the choice is not
difficult. You make the sequel.
</p>
<p>
The problem is that this logic, applied consistently across
an entire industry over twenty years, does not produce a
stable business. It produces a slow erosion of the thing
that made the business worth having in the first place.
Audiences do not go to cinemas to see sequels to sequels.
They go because occasionally something extraordinary happens
on a screen and they want to be in the room when it does.
Strip out the extraordinary, and you have stripped out the
reason to show up.
</p>
<h2>The Franchise as a Business Model</h2>
<p>
The Matrix is a precise case study because the original
trilogy was, by design, a complete thing. Three films with
a beginning, a middle, and an ending. The story resolved.
The characters finished where they were going. Whatever
criticisms one might make of Reloaded or Revolutions, the
trilogy had the structural integrity of something that knew
it was going to end. That is increasingly rare, and it
matters - because an ending is what separates a story from
a content pipeline.
</p>
<p>
The correct response to owning the Matrix IP after the
trilogy was either to leave it alone or to build something
genuinely new inside the same universe. The world the
Wachowskis constructed is large enough. New characters, a
different era, a different corner of the machine war, a
different question about the nature of the simulation - any
of these could have been the basis for something that
respected the original while standing on its own. A current
audience does not need Neo. It needs a new conflict with
real stakes and a story it has not already seen.
</p>
<p>
What Warner Bros. produced instead was The Matrix
Resurrections in 2021 - eighteen years after the trilogy
ended - with the same lead actors, the same Agent Smith,
and a plot that literally resurrects the characters the
previous films had concluded. The decision to bring back
the same faces was not a creative one. It was a calculation
that name recognition and nostalgia would lower the
perceived risk of the investment. The studio did not believe
in the universe. It believed in the brand. The audience
noticed. The film failed - not because the Matrix concept
is exhausted, but because the film offered nothing except
proof that the people making it were not willing to find
out whether it could work without a safety net.
</p>
<p>
Star Wars industrialised this dynamic at a scale that was
previously unimaginable. The acquisition of Lucasfilm was
not a creative decision - it was a content library acquisition.
What followed was the systematic conversion of a mythology
that had genuine cultural weight into a production pipeline.
Films, series, spin-offs, prequels, sequels, all released
on a cadence driven by release windows and subscriber targets
rather than by whether anyone had something worth saying.
The audience eventually signalled, clearly, that it was
exhausted. The response was not to slow down - it was to
try different combinations of the same components.
</p>
<blockquote>
A franchise is not a creative decision.<br />
It is a <span>risk management strategy</span> dressed as one.
</blockquote>
<h2>Marvel and the Pipeline Fallacy</h2>
<p>
The Marvel Cinematic Universe is the most important case
study in modern film economics because it succeeded
spectacularly for long enough that the people running it
drew the wrong conclusion from their own success. From Iron
Man in 2008 through to Endgame in 2019, the MCU produced
something genuinely unusual: a connected series of films
that were, individually, mostly good, and collectively
something audiences had never experienced before. The
shared universe worked. Characters crossed over. Threads
built across years. When it paid off, it paid off at a
scale that reshaped the entire industry's assumptions about
what a film could be.
</p>
<p>
The mistake was in identifying why it worked. The shared
universe was not the product. It was the reward for the
product. Audiences invested in the connections between
films because the films themselves had earned that
investment. Iron Man was a good film. The Winter Soldier
was a good film. Guardians of the Galaxy was a good film.
Each one worked as a standalone experience, which meant
that when they intersected, the intersection meant
something. The connective tissue had weight because the
individual pieces had weight.
</p>
<p>
What Marvel - and Disney, which had acquired it - took
from this was a different lesson: that the pipeline itself
was the asset. That releasing four films and multiple
Disney+ series per year, all tagged as MCU content, would
sustain audience engagement indefinitely because the brand
had accumulated enough goodwill to carry anything attached
to it. This is a category error. Goodwill is not a
renewable resource. It is the residue of past quality,
and it depletes every time it is spent on something that
does not deserve it.
</p>
<p>
The post-Endgame MCU inherited an audience that had spent
eleven years being rewarded for paying attention. What it
offered in return was volume. Films that were
competently assembled but existed primarily to move pieces
into position for the next film. Disney+ series that were
mandatory viewing if you wanted to understand the cinema
releases, effectively converting a leisure choice into
homework. Characters introduced and abandoned. Storylines
launched and quietly dropped. The shared universe, which
had once been the payoff for engagement, became the reason
engagement was required just to follow along. The audience
began to disengage - not because superhero films stopped
being possible, but because the implicit contract had been
broken. The pipeline had forgotten that it was dependent
on the films being good, not merely connected.
</p>
<p>
Streaming did not fix this dynamic. It accelerated it and
added a new and more corrosive incentive on top: subscriber
acquisition. A studio making theatrical films needs a film
to be good enough that people will pay to see it. A streaming
platform needs content to exist in sufficient volume and
variety that enough different people will start a subscription
and not cancel it. These are genuinely different problems,
and the second one does not require quality - it requires
surface area.
</p>
<p>
Altered Carbon is a personal example of what this looks
like from the other side. The first season was genuinely
good - ambitious production design, a coherent adaptation
of its source material, the kind of science fiction that
takes its own premise seriously. I liked it. I kept
watching. I was exactly the subscriber Netflix should want
to retain. The show was cancelled after two seasons
regardless, and I eventually cancelled the subscription.
Those two facts are more connected than they might appear.
</p>
<p>
The issue is not that Netflix made a bad decision by its
own logic. The issue is what its logic measures. A
subscriber who genuinely loves three or four ambitious
series and watches them carefully registers identically to
a subscriber who half-watches twenty shows and never
finishes any of them - as long as both keep paying. The
metric that matters is new sign-ups, because that is what
the market rewards. Retention of engaged viewers who care
about specific content is a secondary concern at best.
Cancelling Altered Carbon did not cost Netflix the number
it was optimizing for. It cost them me - eventually - and
I was never the unit being counted.
</p>
<p>
What this produces is a library optimized not for quality
or loyalty but for the minimum threshold of good enough to
keep someone paying while the next acquisition drives new
sign-ups. The service does not need you to love it. It
needs you to not quite hate it enough to cancel. That is a
meaningful distinction. A library built around that
incentive looks very different from one built around making
things worth watching - and over time, the difference
becomes visible.
</p>
<div class="table-wrap">
<table class="attack-table">
<thead>
<tr>
<th>Model</th>
<th>Primary incentive</th>
<th>What it produces</th>
</tr>
</thead>
<tbody>
<tr>
<td>Theatrical studio</td>
<td>
Maximise opening weekend and franchise
extension potential
</td>
<td class="counter">
IP acquisitions, sequels, safe casting,
risk-averse greenlight decisions
</td>
</tr>
<tr>
<td>Streaming platform</td>
<td>
Subscriber acquisition and retention
across the broadest possible audience
</td>
<td class="counter">
High volume, uneven quality, early
cancellations, content churn
</td>
</tr>
<tr>
<td>Franchise extension</td>
<td>
Extract value from an established
property at minimal creative cost
</td>
<td class="counter">
Diminishing quality per instalment,
audience fatigue, brand erosion
</td>
</tr>
<tr>
<td>AI-assisted production</td>
<td>
Reduce per-unit content cost to near
zero while maintaining surface coverage
</td>
<td class="counter">
Structural collapse of the floor —
unlimited slop at no marginal cost
</td>
</tr>
</tbody>
</table>
</div>
<h2>AI Is Where This Was Always Going</h2>
<p>
The question AI puts to the industry is not a technical
one. It is a choice: do we hire a writer, or don't we?
Do we commission a director with a specific vision, or do
we generate the output that vision would have produced?
That choice is now explicit in a way it was not before.
And given everything the industry has already demonstrated
about its priorities - the franchise logic, the subscriber
metrics, the systematic elimination of creative risk —
there is not much reason to expect most studios and
platforms to choose the human when the alternative is
cheaper and more controllable.
</p>
<p>
This is what artists have been worried about, and they are
right - but not quite for the reason usually given. The
concern is often framed as AI producing bad films. The more
accurate concern is that AI produces films optimised for
the same thing the industry already optimises for: volume,
familiarity, minimum acceptable quality. AI does not
introduce a new set of values into the pipeline. It
enforces the existing ones more efficiently. A model
trained on what has performed before will reliably produce
more of what has performed before. For an industry that has
spent two decades treating proven formula as the safest
bet, that is not a warning. It is a feature.
</p>
<p>
What gets lost is not quality in the narrow sense - an
AI can produce something watchable, and watchable is
already the threshold most of the industry is aiming for.
What gets lost is the possibility of something genuinely
unexpected. A writer or director brings a specific
perspective that did not exist before, that cannot be
interpolated from what came before, and that occasionally
produces something the market did not know it wanted until
it arrived. The first Matrix was that. The early MCU was
that. The pipeline, fully enforced by AI, eliminates the
conditions under which that becomes possible. Every film
becomes a variation on the centroid of everything that
already worked. The ceiling does not disappear - it just
gets permanent.
</p>
<h2>What Would Fix It</h2>
<p>
The honest answer is that the incentive structure would
need to change, and there is no obvious mechanism by which
that happens from inside the industry. The studios that
consistently produce the best work are either small enough
to be insulated from franchise logic, backed by individuals
with enough capital and conviction to override short-term
return calculations, or operating in markets where the
economics of Hollywood do not apply in the same way.
</p>
<p>
Audiences have some leverage. Theatrical attendance is a
direct signal. Not watching a streaming series past its
first episode is a direct signal. The platforms and studios
read these signals carefully - they just tend to read them
as evidence that they need better marketing rather than
better films. That misreading is itself a symptom of an
industry that has fully internalised the idea that the
product is secondary to the pipeline.
</p>
<hr />
<p>
Cinema at its best is one of the few experiences that cannot
be fully replicated on a phone screen with half your
attention elsewhere. The industry has spent twenty years
making content that can be. That is not an accident. It is
what you get when every structural incentive points toward
volume, safety, and extraction - and nobody in a position
to change it has a strong enough reason to try.
</p>
</article>
<footer>
<a href="mailto:me@samantha42.xyz">me@samantha42.xyz</a>
<p>© 2026 — All rights reserved</p>
</footer>
</div>
</body>
</html>
+318
View File
@@ -0,0 +1,318 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Cybersecurity — Samantha Vero Friis</title>
<link
href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&family=Syne:wght@400;700;800&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="/static/style/styles.css" />
</head>
<body>
<div class="container--narrow">
<header>
<a href="/" class="back">← back</a>
<p class="tag">// opinion</p>
<h1>The Weakest<br />Link Is Always<br /><span>Human</span></h1>
<div class="byline">
<span>Samantha Vero Friis</span>
<span></span>
<span>Cybersecurity</span>
</div>
</header>
<article>
<p class="lead">
Most breaches are not the result of a sophisticated exploit
or a zero-day vulnerability. They are the result of a person
clicking the wrong link, reusing a password, or trusting a
phone call they should not have answered. Security is not
primarily a technical problem. It is an organizational one.
</p>
<h2>Know What You Are Protecting</h2>
<p>
Before you can build a security posture, you need to
understand your data. Not all information carries the same
weight. Customer financial records, internal credentials,
and strategic planning documents are not the same as a
public press release or a shared meeting agenda. Treating
all data as equally sensitive wastes resources and creates
compliance fatigue. Treating it all as equally safe is how
companies end up in the news.
</p>
<p>
The first step is classification. Every organization should
have a clear and enforced taxonomy for what they hold — what
is critical, what is sensitive, and what is routine. Once
data is classified, access decisions, storage choices, and
transmission policies all become considerably more
straightforward.
</p>
<div class="classify-grid">
<div class="classify-card">
<p class="level level-critical">● Critical</p>
<h3>Restricted</h3>
<p>
Credentials, financial records, legal documents,
private keys. Encrypted at rest and in transit.
Need-to-know access only. Audited on access.
</p>
</div>
<div class="classify-card">
<p class="level level-high">● Sensitive</p>
<h3>Internal</h3>
<p>
Strategy documents, personnel files, client data.
Role-based access. Not to leave internal systems
without explicit sign-off.
</p>
</div>
<div class="classify-card">
<p class="level level-standard">● Standard</p>
<h3>General</h3>
<p>
Shared documentation, public-facing content,
operational guides. Normal access controls. No
special handling required.
</p>
</div>
</div>
<h2>Passwords Are Not a Personal Choice</h2>
<p>
The idea that employees should be trusted to choose and
manage their own passwords is one of the most persistent and
destructive myths in corporate security. People are not good
at passwords. They reuse them across services, they write
them down, they choose predictable patterns, and they resist
changing them when asked. This is not a character flaw — it
is human nature operating against a task humans were never
designed to do well.
</p>
<p>
The answer is a mandatory, company-wide password manager.
Not recommended. Not optional. Mandatory. Every account
created for company use lives in the vault. Credentials are
generated by the manager — long, random, unique per service.
No employee ever needs to know or remember most of them.
Access to the vault itself is protected by a strong master
passphrase and hardware-backed two-factor authentication.
</p>
<blockquote>
A password an employee <span>cannot remember</span> is a
password an attacker <span>cannot guess</span>.
</blockquote>
<h2>Two-Factor Is Not Optional</h2>
<p>
Two-factor authentication is the single highest-return
security investment any organization can make. A stolen
password without the second factor is nearly useless. Yet an
astonishing number of companies treat 2FA as something to
enable "if employees want to." That framing gets it exactly
backwards.
</p>
<p>
Every account with access to company systems requires 2FA,
enforced at the policy level — not left to the individual.
Preference should go to hardware security keys like
<code>YubiKey</code> or authenticator apps generating
time-based codes. SMS-based 2FA is better than nothing, but
SIM-swap attacks make it the weakest option and it should be
phased out wherever possible.
</p>
<p>
Enrollment should happen on day one, before access is
granted to any system. Not after. The moment an employee has
a credential without a second factor is a window of exposure
that does not need to exist.
</p>
<h2>Make Access Deliberately Hard</h2>
<p>
This sounds counterintuitive. Friction is the enemy of
productivity — until it is the thing standing between your
database and someone who found a badge in the parking lot.
The principle of least privilege is not just a compliance
checkbox. It is an architecture decision.
</p>
<p>
Employees should have access to exactly what they need to do
their job, and nothing more. Access should be reviewed on a
schedule and revoked immediately upon role change or
departure. Broad access to critical systems should require
explicit justification, a second approver, and a time limit.
The default answer to an access request is not yes — it is:
why do you need this, for how long, and who else knows?
</p>
<p>
This friction is not about distrust. It is about reducing
the blast radius of any single compromised account. If an
attacker gains access to a junior analyst's credentials,
they should find themselves in a narrow corridor, not a wide
open floor.
</p>
<h2>Social Engineering Is the Real Attack Surface</h2>
<p>
Technical controls can be near-perfect and still fail the
moment someone calls the help desk pretending to be a
manager who has been locked out while traveling. Social
engineering works because it exploits social instincts that
are otherwise useful — the tendency to help, to defer to
authority, to avoid confrontation.
</p>
<p>
Mitigating this requires two things: process and culture.
Process means that no credential reset, no access grant, and
no sensitive disclosure ever happens over a phone call or an
email alone, regardless of how urgent it sounds or who the
request appears to come from. Verification must follow a
separate, pre-established channel. The louder someone
insists the situation is urgent, the more carefully the
process should be followed.
</p>
<p>
Culture means that employees feel safe slowing down and
saying no. An employee who challenges an unusual request
should be recognized for it, not made to feel obstructive.
The organization needs to make it socially acceptable — even
admirable — to be the person who held the line.
</p>
<div class="table-wrap">
<table class="attack-table">
<thead>
<tr>
<th>Attack Vector</th>
<th>Method</th>
<th>Countermeasure</th>
</tr>
</thead>
<tbody>
<tr>
<td>Phishing</td>
<td>
Fraudulent email mimicking a trusted sender
to harvest credentials or deploy malware
</td>
<td class="counter">
Email filtering, DMARC/DKIM, regular drill
simulations, no credential entry from email
links
</td>
</tr>
<tr>
<td>Vishing</td>
<td>
Voice call impersonating IT, management, or
a vendor to extract access or trigger an
action
</td>
<td class="counter">
Zero verbal credential resets. All requests
verified via a separate known channel, no
exceptions
</td>
</tr>
<tr>
<td>Credential Stuffing</td>
<td>
Automated use of leaked passwords from other
breaches against company systems
</td>
<td class="counter">
Mandatory password manager with unique
credentials per service. 2FA blocks the rest
</td>
</tr>
<tr>
<td>Insider Threat</td>
<td>
Disgruntled or compromised employee
exfiltrating data or granting unauthorized
access
</td>
<td class="counter">
Least-privilege access, access logging and
anomaly alerts, prompt offboarding
procedures
</td>
</tr>
<tr>
<td>Pretexting</td>
<td>
Fabricated scenario (new hire, auditor,
vendor) to gain physical or digital access
</td>
<td class="counter">
Visitor protocols, escort requirements, no
tailgating culture, ID verification for all
physical access
</td>
</tr>
</tbody>
</table>
</div>
<h2>Security Is a Continuous Practice</h2>
<p>
A one-time audit followed by a certificate on the wall is
not a security posture. It is a snapshot. Threat landscapes
change, personnel changes, and systems change. Security
needs to be a living practice — regular access reviews,
periodic credential audits, tabletop exercises for breach
scenarios, and honest post-mortems when something goes
wrong.
</p>
<p>
The companies that handle breaches best are not necessarily
the ones that prevented them — they are the ones that
detected them fast, contained them well, and communicated
clearly. That kind of response capability does not appear on
the day of the incident. It is built over years of treating
security as infrastructure, not an afterthought.
</p>
<hr />
<p>
You cannot patch people. But you can build systems and
cultures that make it very difficult for human error to
become organizational catastrophe. Mandatory 2FA. Mandatory
password managers. Minimal access by default. Verification
processes that do not bend under social pressure. These are
not sophisticated controls. They are disciplined ones — and
discipline, consistently applied, is the closest thing to
security that actually exists.
</p>
</article>
<footer>
<a href="mailto:me@samantha42.xyz"
>me@samantha42.xyz</a
>
<p class="footer-copy">&copy; 2026 — All rights reserved</p>
</footer>
</div>
</body>
</html>
+314
View File
@@ -0,0 +1,314 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FP&amp;A Budgeting Engine — Samantha Vero Friis</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&family=Libre+Caslon+Text:wght@400;700;800&family=Syne:wght@400;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="/static/style/styles.css" />
<style>
/* ── Page-specific layout ── */
header { padding: 2.4rem 0 2rem; }
main { padding: 3rem 0 5rem; }
.prose-block { max-width: 660px; }
pre {
font-family: "DM Mono", monospace;
font-size: 0.78rem;
line-height: 1.8;
background: var(--surface);
border: 1px solid var(--border);
padding: 1.4rem 1.6rem;
overflow-x: auto;
margin: 1.6rem 0;
color: #c8c8c0;
}
/* stat cards at top */
.cards { margin: 2rem 0 2.5rem; }
footer {
border-top: 1px solid var(--border);
padding: 1.6rem 0;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.7rem;
color: var(--muted);
}
footer a { color: var(--muted); text-decoration: none; }
footer a:hover { color: var(--accent); }
@media (max-width: 560px) {
.cards { grid-template-columns: 1fr 1fr; }
}
</style>
</head>
<body>
<!-- ── HEADER ── -->
<header>
<div class="container">
<a class="back" href="/">← back</a>
<div class="tag">project</div>
<h1>FP&amp;A <span>Budgeting Engine</span></h1>
<div class="byline">
<span>Samantha Vero Friis</span>
<span style="color:var(--muted)">Go · REST API · Finance</span>
<span style="color:var(--muted)">March 2026</span>
</div>
</div>
</header>
<!-- ── STAT CARDS ── -->
<div class="container">
<div class="cards">
<div class="card">
<div class="card-label">Language</div>
<div class="card-value" style="color:var(--accent)">Go</div>
</div>
<div class="card">
<div class="card-label">Database</div>
<div class="card-value" style="color:var(--accent)">SQLite</div>
</div>
<div class="card">
<div class="card-label">Architecture</div>
<div class="card-value" style="font-size:1.1rem;color:var(--accent)">REST API</div>
</div>
<div class="card">
<div class="card-label">Infrastructure</div>
<div class="card-value" style="font-size:1.1rem;color:var(--green)">Zero deps</div>
</div>
</div>
</div>
<!-- ── MAIN ── -->
<main>
<div class="container">
<div class="prose-block">
<p class="lead">
A REST API for corporate budget management, actuals ingestion, and variance
reporting — built to replace the manual Excel workflow that smaller finance
teams still run every month. Single binary, no infrastructure, deploys anywhere.
</p>
<h2>The problem it solves</h2>
<p>
At companies below a certain size — typically below 500 employees, or without
the budget for enterprise planning platforms like Anaplan or Adaptive Planning —
the monthly FP&amp;A close still runs on spreadsheets. Actuals are exported from
the ERP, pasted into a workbook, and variances are computed by hand across
departments and GL accounts. The process is slow, error-prone, and breaks the
moment someone edits the wrong cell.
</p>
<p>
Large enterprises have solved this with dedicated tooling. The gap is the
mid-market: companies that have outgrown Excel but cannot justify a six-figure
implementation project. This engine sits in that space — structured schema,
versioned budgets, on-demand actuals ingestion, and instant variance reports
with no pivot tables and no shared file conflicts.
</p>
<blockquote>
The goal was not to build something clever.<br>
It was to build something that solves a real problem<br>
<span>finance teams in the mid-market actually have.</span>
</blockquote>
<h2>Stack</h2>
<p>
Most finance tooling is Python notebooks or Excel macros — useful for analysis,
fragile in production. Go compiles to a single binary with no runtime
dependencies, makes every error path explicit, and handles concurrency safely.
SQLite was chosen over Postgres deliberately: for a tool running inside a
finance team's environment, zero infrastructure matters. The database is a
single file. Backups are a file copy.
</p>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Layer</th>
<th style="text-align:left">Choice</th>
<th style="text-align:left">Reason</th>
</tr>
</thead>
<tbody>
<tr>
<td class="ticker">Language</td>
<td style="text-align:left">Go</td>
<td style="text-align:left;color:var(--muted)">Single binary, no runtime, explicit errors</td>
</tr>
<tr>
<td class="ticker">Routing</td>
<td style="text-align:left">net/http (stdlib)</td>
<td style="text-align:left;color:var(--muted)">No deps; method+path routing since Go 1.22</td>
</tr>
<tr>
<td class="ticker">Database</td>
<td style="text-align:left">SQLite</td>
<td style="text-align:left;color:var(--muted)">Zero infrastructure, file-based, ships with binary</td>
</tr>
<tr>
<td class="ticker">Schema</td>
<td style="text-align:left">Auto-migrated</td>
<td style="text-align:left;color:var(--muted)"><code>CREATE TABLE IF NOT EXISTS</code> on startup</td>
</tr>
</tbody>
</table>
</div>
<h2>API surface</h2>
<p>
Three resource groups. Budgets support full CRUD by department, GL account,
fiscal period, and version. Actuals are ingested via a single upsert endpoint
— idempotent by period, department, and GL code, designed to accept ERP
JSON exports directly. Variance endpoints return computed reports immediately.
</p>
<pre>POST /api/v1/budgets Create a budget line
PUT /api/v1/budgets/{id} Update amount or notes
DELETE /api/v1/budgets/{id} Remove a budget line
POST /api/v1/actuals/ingest Upsert an actual (idempotent)
GET /api/v1/variance Full variance report
GET /api/v1/variance/alerts Lines exceeding threshold
GET /api/v1/health Returns 200 if DB is reachable</pre>
<p>
Variance endpoints accept <code>year</code>, <code>period</code>,
<code>dept</code>, <code>version</code>, and <code>threshold</code>
as query parameters.
</p>
<h2>Finance concepts implemented correctly</h2>
<p>
<strong style="color:var(--text)">Favourability logic.</strong> Whether a
variance is good or bad depends on account type. Revenue accounts are
favourable when actuals exceed budget; cost accounts when actuals come in
under. The engine handles both via a <code>favour_high</code> flag per GL
account — a distinction generic reporting tools consistently leave to manual
configuration.
</p>
<p>
<strong style="color:var(--text)">Budget versioning.</strong> Original budget
and up to three forecast revisions are stored separately, enabling a full
budget vs. forecast vs. actual three-way comparison without overwriting
history — the standard FP&amp;A reforecast workflow.
</p>
<p>
<strong style="color:var(--text)">GL account typing.</strong> Accounts are
typed as <code>revenue</code>, <code>cogs</code>, <code>opex</code>,
<code>capex</code>, or <code>headcount</code>. This drives the P&amp;L
rollup structure and the favourability logic together, reflecting how an
actual chart of accounts is structured.
</p>
<p>
<strong style="color:var(--text)">Fiscal period decoupling.</strong> Periods
are stored as integers 112, decoupled from calendar months — required for
any company not on a January fiscal year.
</p>
<h2>Example output</h2>
<p>
Engineering department, FY2024 P09. Cloud infrastructure came in under budget.
Consulting exceeded by 51%.
</p>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>GL</th>
<th>Description</th>
<th>Budget</th>
<th>Actual</th>
<th>Var %</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>6100</td>
<td style="text-align:left;color:var(--text)">Salaries &amp; Wages</td>
<td>4,200k</td>
<td>4,380k</td>
<td class="down">4.29%</td>
<td class="down">unfavourable</td>
</tr>
<tr>
<td>6500</td>
<td style="text-align:left;color:var(--text)">Consulting &amp; Contractors</td>
<td>450k</td>
<td>680k</td>
<td class="down">51.11%</td>
<td class="down">unfavourable</td>
</tr>
<tr>
<td>5000</td>
<td style="text-align:left;color:var(--text)">Cloud Infrastructure</td>
<td>850k</td>
<td>791k</td>
<td class="up">+6.94%</td>
<td class="up">favourable</td>
</tr>
</tbody>
</table>
</div>
<h2>Running it</h2>
<pre>git clone https://git.samantha42.xyz/samantha/FPandA-Engine
cd FPandA-Engine
cp .env.example .env
go mod tidy
go run ./cmd/server
# → http://localhost:8080
# → fpa.db created on first run</pre>
<p>
Two environment variables: <code>DB_PATH</code> (use <code>:memory:</code>
for tests) and <code>PORT</code>. Demo seed data for Engineering, Sales,
and Marketing is included in <code>scripts/seed_demo.sql</code>.
</p>
<hr />
<p>
Source at
<a href="https://git.samantha42.xyz/samantha/FPandA-Engine"
style="color:var(--accent);text-decoration:none"
target="_blank">git.samantha42.xyz/samantha/FPandA-Engine</a>
</p>
</div>
</div>
</main>
<footer>
<div class="container" style="display:flex;justify-content:space-between;width:100%">
<a href="mailto:me@samantha42.xyz">me@samantha42.xyz</a>
<span>© 2026 — All rights reserved</span>
</div>
</footer>
</body>
</html>
+265
View File
@@ -0,0 +1,265 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Consensus Is the Problem — Samantha Vero Friis</title>
<link
href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&family=Syne:wght@400;700;800&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="/static/style/styles.css" />
</head>
<body>
<div class="container--narrow">
<header>
<a href="/" class="back">← back</a>
<p class="tag">// opinion</p>
<h1>Consensus Is<br />the<br /><span>Problem</span></h1>
<div class="byline">
<span>Samantha Vero Friis</span>
<span></span>
<span>Finance &amp; Investing</span>
</div>
</header>
<article>
<p class="lead">
A stock price is not a summary of a company's fundamentals.
It is the market's current best guess about the future.
The price already contains expectations. The question worth
asking is whether those expectations are right.
</p>
<h2>What a Price Is Really Saying</h2>
<p>
Every valuation implies a story. A company trading at forty
times earnings is not just expensive — it is telling you that
the market believes something specific: that growth will remain
high for long enough, that margins will hold, that the
competitive position is durable. Unpack the multiple into its
components and you have a set of forecasts baked into the
price. The investor's job is to decide whether those forecasts
are reasonable.
</p>
<p>
This reframing matters because it shifts the question from
"is this stock cheap?" to "is this story credible?" Cheap
stocks can be cheap for good reason. Expensive stocks can be
undervalued if the embedded assumptions are too conservative.
Value, in this sense, is not a property of price alone — it
is the gap between what the price implies and what reality is
likely to deliver.
</p>
<h2>The Consensus Problem</h2>
<p>
Markets aggregate information quickly and, on average, do it
well. But consensus is not the same as correctness. Consensus
is the average of what a large number of participants currently
believe, weighted by their capital. It reflects what is known,
what is legible, and what is socially acceptable to believe.
It is systematically slow to update on things that are
ambiguous, uncomfortable, or genuinely novel.
</p>
<p>
When an investment thesis is consensus — when every analyst
covers the stock, every major fund holds it, and the positive
narrative is repeated on every earnings call — the upside is
already in the price. There is no one left to convince. A
buyer who enters at that point is not getting paid for a
correct view. They are paying for the privilege of agreeing
with everyone else.
</p>
<blockquote>
The consensus trade is <span>already priced</span>.<br />
Only disagreement can <span>generate alpha</span>.
</blockquote>
<p>
This is the central problem with conventional value investing
as it is commonly practiced today. The stocks that look most
obviously cheap are often the ones where the consensus has
already decided they deserve to be cheap. The screening
criteria that identify "value" are, by construction, widely
known and widely applied. The edge that came from running
those screens in 1975 does not exist in the same form now.
</p>
<h2>Where Mispricing Actually Lives</h2>
<p>
Genuine mispricing tends to occur in situations where the
consensus view is wrong in a specific and identifiable way,
and where that wrongness has not yet been corrected. This
can happen for several structural reasons.
</p>
<p>
Institutional constraints create systematic blind spots. Fund
managers are evaluated on short cycles, penalized for holding
unfashionable names, and often prohibited from owning
securities outside their mandate. These pressures push capital
away from certain situations not because the economics are bad
but because the social and career costs of being wrong there
are high. The result is that some assets are systematically
underowned relative to their intrinsic value.
</p>
<p>
Narrative lag is another source. Companies undergoing genuine
change — a new management team, a shift in cost structure, an
improving competitive position — often continue to be priced
against their old story long after the underlying reality has
shifted. The market is very good at updating on quantitative
signals and very slow at updating on qualitative ones.
</p>
<p>
Complexity discounts are real and often excessive. A business
that is hard to model, that operates across multiple segments,
or that sits at the intersection of industries analysts do not
cover together will often trade at a lower multiple than a
simpler business with identical economics. That discount is not
always irrational — complexity carries risk — but it is often
overdone.
</p>
<div class="table-wrap">
<table class="attack-table">
<thead>
<tr>
<th>Source</th>
<th>Why consensus gets it wrong</th>
<th>What to look for</th>
</tr>
</thead>
<tbody>
<tr>
<td>Institutional constraints</td>
<td>
Capital avoids the name for structural,
not economic, reasons
</td>
<td class="counter">
Forced sellers, mandate mismatches,
index exclusions
</td>
</tr>
<tr>
<td>Narrative lag</td>
<td>
Price reflects the old story; reality
has already changed
</td>
<td class="counter">
Management change, cost restructuring,
improving unit economics
</td>
</tr>
<tr>
<td>Complexity discount</td>
<td>
Analysts underweight what they cannot
easily model
</td>
<td class="counter">
Conglomerates, cross-sector businesses,
opaque but healthy cash flows
</td>
</tr>
<tr>
<td>Sentiment overshoot</td>
<td>
Fear or enthusiasm has moved price far
beyond the rational range
</td>
<td class="counter">
Implied assumptions that require
implausible outcomes to justify current price
</td>
</tr>
</tbody>
</table>
</div>
<h2>The Role of Implied Expectations</h2>
<p>
The most disciplined version of this approach is to work
backwards from price. Rather than building a model and
concluding that a stock is cheap or expensive, start by
asking: what does this price require? What growth rate, what
margin profile, what reinvestment assumption would you need
to believe to justify paying what the market is asking today?
</p>
<p>
If the answer requires a long run of performance with no
credible foundation in the company's history, competitive
position, or industry structure — the price is wrong in one
direction. If the assumptions are so pessimistic that even a
mediocre outcome would beat them, it is wrong in the other.
</p>
<p>
This framing makes the investment case explicit and
falsifiable. It forces a specific disagreement with the
market rather than a vague sense that something is cheap or
expensive. And it clarifies what would need to be true for
the thesis to fail — which is at least as important as
knowing what would need to be true for it to succeed.
</p>
<h2>On Being Wrong in Public</h2>
<p>
A genuinely contrarian position is, by definition, one that
the consensus thinks is mistaken. Holding it requires being
comfortable with the fact that most informed observers
currently disagree with you, and that some period of time
will pass before — if — the price reflects the view you hold.
That discomfort is not incidental to the strategy. It is the
mechanism by which the return is generated. If the position
were comfortable, it would be consensus, and it would already
be priced.
</p>
<p>
This does not mean contrarianism for its own sake is sound.
Being different from the consensus is not the same as being
right. The goal is not to disagree with the market but to
identify specific, well-reasoned cases where the market's
current view is demonstrably wrong — and where the gap
between implied expectations and probable reality is large
enough to be worth the risk of being early, or simply
incorrect.
</p>
<hr />
<p>
Value is not a style. It is a discipline applied wherever
the math supports it. The companies that look most like
"value stocks" by conventional screens are often the ones
where the opportunity has already been arbitraged away. The
real work is in finding the situations where the consensus
has constructed a story the underlying economics cannot
support — or has failed to construct one that they clearly
do. That gap, wherever it appears, is where returns come
from.
</p>
</article>
<footer>
<a href="mailto:me@samantha42.xyz">me@samantha42.xyz</a>
<p>© 2026 — All rights reserved</p>
</footer>
</div>
</body>
</html>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

+208
View File
@@ -0,0 +1,208 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Samantha Vero Friis</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500&family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap"
rel="stylesheet" />
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
<link rel="stylesheet" href="/static/output.css"/>
</head>
<body class="theme-root day " id="themeRoot">
<nav>
<a class="nav-logo" href="/#">samantha_vero_friis</a>
</nav>
<div class="shell">
<aside>
<div class="row">
<img id="day" src="/static/icons/lovesun.png" width="30px" class="hidden" onclick="makeday()" >
<img id="night" src="/static/icons/moon.png" width="30px" onclick="makenight()" >
</div>
<div>
<p class="sidebar-label">navigate</p>
<div class="sidebar-links">
<a href="#about">about</a>
<a href="#experience">experience</a>
<a href="#education">education</a>
<a href="#interests">interests</a>
</div>
</div>
<div>
<p class="sidebar-label">pages</p>
<div class="sidebar-links">
<a href="/automation">automation</a>
<a href="/research">research</a>
<a href="/bookkeeping">bookkeeping</a>
<a href="/interests">interests</a>
</div>
</div>
<div>
<p class="sidebar-label">skills</p>
<div class="skill-list">
<div class="skill-item">Excel</div>
<div class="skill-item">Python</div>
<div class="skill-item">SQL</div>
<div class="skill-item">Go</div>
<div class="skill-item">Git</div>
<div class="skill-item">Linux</div>
</div>
</div>
<div>
<p class="sidebar-label">location</p>
<div style="font-family:var(--ff-mono);font-size:0.75rem;color:var(--muted);">Denmark<br>Aalborg Øst, 9220</div>
</div>
<div>
<p class="sidebar-label">languages</p>
<div class="lang-row">
<span class="lang-badge">Danish</span>
<span class="lang-badge">English</span>
</div>
</div>
<div style="flex: 1;"></div>
</aside>
<main id="about">
<section class="hero">
<p class="hero-eyebrow">About Me · 2026</p>
<h1>Samantha<br><span>Vero Friis</span>
</h1>
<p class="hero-bio">Bachelor's student in Business Administration seeking an entry-level analyst or
finance-related role while studying. Strong numerical skills, advanced Excel, and experience in company, risk,
and financial analysis. Analytical, detail-oriented, and efficiency-focused.</p>
<div class="hero-meta">
<span>
<div hx-get="/status" hx-trigger="load" hx-swap="outerHTML"></div>
<span class="exp-tag">samantha42.xyz</span>
<span class="exp-tag">me@samantha42.xyz</span>
</div>
</section>
<section id="experience">
<div class="section-head">
<h2>experience</h2>
</div>
<div class="exp-grid">
<div class="exp-card link" onclick="window.location.href='/research'">
<div class="exp-header">
<span class="exp-title">Independent Equity Research</span>
<span class="exp-tag">finance</span>
</div>
<p class="exp-body">Fundamental analysis using SEC filings (10-K, 10-Q) and earnings reports. Assess
profitability, liquidity, risk factors, and long-term business sustainability. Handle incomplete or
low-transparency information through structured cross-validation.</p>
</div>
<div class="exp-card link" onclick="window.location.href='/bookkeeping'">
<div class="exp-header">
<span class="exp-title">Bookkeeping &amp; Administration</span>
<span class="exp-tag">Record keeping</span>
</div>
<p class="exp-body">Excel to In house Bookkeeping tooling and backups and safe sql databse for larger scale. made to better comply to lawmakers in KLACQ ApS(small self owned company) currently selling SaaS products. <a href="https:klacq.eu">klacq website</a> </p>
</div>
<div class="exp-card link" onclick="window.location.href='/automation'">
<div class="exp-header">
<span class="exp-title">Technical Tools &amp; Automation</span>
<span class="exp-tag">programing</span>
</div>
<p class="exp-body">Build lightweight backend tools to automate repetitive analytical and operational tasks.
Python for data processing, validation, and workflow automation. Lightweight online services in Go,
focusing on speed and reliability.</p>
</div>
</div>
</section>
<section id="education">
<div class="section-head">
<h2>education</h2>
</div>
<div class="edu-grid">
<div class="edu-card">
<div class="edu-year">2025 — present</div>
<div>
<div class="edu-degree">Bachelor of Economics &amp; Business Administration (HA)</div>
<div class="edu-inst">Aalborg University · Expected 2028</div>
</div>
</div>
<div class="edu-card">
<div class="edu-year">2024 — 2025
<br>
<span class="edu-tag tag-dropped">discontinued</span>
</div>
<div class="edu-body">
<div class="edu-degree">Mathematics-Technology (BSc)</div>
<div class="edu-inst">Aalborg University · 1 semester</div>
</div>
</div>
<div class="edu-card">
<div class="edu-year">2020 — 2023</div>
<div>
<div class="edu-degree">HTX Technical Upper Secondary Education</div>
<div class="edu-inst">Viden Gymnasier</div>
<div class="edu-detail">Mathematics A, Biology B · Study project: Mathematics &amp; Technology</div>
</div>
</div>
</div>
</section>
<section id="interests">
<div class="section-head">
<h2>interests</h2>
</div>
<div class="exp-grid">
<div class="exp-card link" onclick="window.location.href='/interests'">
<div class="exp-header">
<span class="exp-title">Personal Free Time Interest</span>
<span class="exp-tag">Personal</span>
</div>
<p class="exp-body">Movies, computers, PC gaming and oard games</p>
</div>
</div>
</section>
</main>
</div>
<footer>
<div class="footer-contact">
<a href="mailto:me@samantha42.xyz">me@samantha42.xyz</a>
</div>
<div class="footer-copy">© 2026 — all rights reserved</div>
</footer>
<script>
// just some fun.
console.log("%c👀 hi there!", "font-size:20px")
function makeday() {
themeRoot.classList.remove('night');
const day = document.getElementById('day');
const night = document.getElementById('night');
day.classList.add("hidden");
night.classList.remove("hidden");
}
function makenight() {
themeRoot.classList.add('night');
const day = document.getElementById('day');
const night = document.getElementById('night');
day.classList.remove("hidden");
night.classList.add("hidden");
}
</script>
</body>
</html>
+197
View File
@@ -0,0 +1,197 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Infrastructure — Samantha Vero Friis</title>
<link
href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&family=Syne:wght@400;700;800&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="/static/style/styles.css"/>
</head>
<body>
<div class="container">
<header>
<a href="/" class="back">← back</a>
<p class="tag">// opinion</p>
<h1>Convenience<br />Is Making Us<br /><span>Worse</span></h1>
<div class="byline">
<span>Samantha Vero Friis</span>
<span></span>
<span>Computer Infrastructure</span>
</div>
</header>
<article>
<p class="lead">
The tools a company chooses to build on top of says a lot
about how serious it is about the craft. The industry-wide
drift toward Word, Teams, and Microsoft 365 is not a
productivity upgrade — it is a slow erosion of competence
dressed up as convenience.
</p>
<h2>The Convenience Trap</h2>
<p>
Every tool that hides complexity from you takes something
away. When a new hire can open a laptop running Windows,
install Office in ten minutes, and be "operational" by noon,
that feels like a win. But operational is not the same as
capable. What that person has actually learned is how to
navigate a UI. They have not learned how their machine
works, how their data flows, or how to adapt when the
abstraction breaks.
</p>
<p>
Large vendor ecosystems like Microsoft 365 are engineered
for the lowest common denominator. They are excellent at
onboarding someone with no background — and they stop there.
The ceiling is built in by design. You cannot meaningfully
extend Word. You cannot script Teams in any serious way. You
are a tenant inside someone else's system, paying monthly
for the privilege of not understanding what is happening
underneath you.
</p>
<blockquote>
Convenience optimizes for the speed of the
<span>newcomer</span>.<br />
It does not care about the ceiling of the
<span>expert</span>.
</blockquote>
<h2>What We Trade Away</h2>
<p>
The deeper issue is what gets lost over time. A team that
has never had to configure anything has never had to
understand anything. When your document pipeline is Word
plus SharePoint, you never learn what a proper typesetting
system looks like. When your servers are Azure with a
click-through UI, you never develop intuition for
networking, permissions, or failure modes. The knowledge
simply does not accumulate.
</p>
<p>
The companies most dependent on big vendor stacks also tend
to be the most fragile. A license change, a price increase,
a deprecated feature — and suddenly nobody knows what to do.
There is no internal knowledge to fall back on. The stack
was always a rented abstraction.
</p>
<div class="compare">
<div class="compare-col bad">
<h3>Vendor-Locked Stack</h3>
<ul>
<li>Fast to start, hard to master</li>
<li>Knowledge lives in the UI, not the team</li>
<li>Expensive at scale, locked by contracts</li>
<li>No meaningful customization</li>
<li>Fragile when the vendor changes terms</li>
<li>Homogeneous — everyone works the same way</li>
</ul>
</div>
<div class="compare-col good">
<h3>Custom Infrastructure</h3>
<ul>
<li>Slower to start, no ceiling</li>
<li>Knowledge compounds inside the team</li>
<li>Cheap at scale, you own what you build</li>
<li>Configured precisely for your workflow</li>
<li>Resilient — you understand every layer</li>
<li>Personal — the environment fits the person</li>
</ul>
</div>
</div>
<h2>Linux as a Philosophy</h2>
<p>
A Linux desktop is not just a different operating system. It
is a different relationship with your machine. You configure
your window manager, your keybindings, your terminal, your
dotfiles. The environment becomes an extension of how you
think. Two engineers on the same team can have entirely
different setups that are both optimized for their own
cognitive style — and that is a feature, not a problem.
</p>
<p>
This is not about being difficult or contrarian. It is about
the fact that a person who has spent time configuring
<code>neovim</code>, managing a
<code>systemd</code> service, or writing their own shell
scripts has fundamentally more understanding of computation
than someone who has spent the same time clicking through
GUIs. That understanding compounds. It transfers across
every problem they touch.
</p>
<h2>Documents Should Be Code</h2>
<p>
The same logic applies to document production. Word
processing is a GUI wrapped around formatting decisions you
did not make. The output is inconsistent, the versioning is
painful, and the result is never quite right. A typesetting
system like <code>Typst</code> treats a document as source
code — deterministic, version-controlled, composable. You
write a template once and it is correct everywhere, forever.
The document becomes a reproducible artifact.
</p>
<p>
Custom pipelines like this require an upfront investment.
You have to learn the tool, build the template, establish
the workflow. But once built, the leverage is enormous.
Reports, invoices, analysis documents — all generated
cleanly from structured data, with zero manual formatting.
That is not possible in Word. It is not even imaginable in
Word.
</p>
<h2>Own Your Servers</h2>
<p>
Self-hosted infrastructure demands that someone in your
organization understands it. That is not a burden — it is
the point. When you run your own services, you know exactly
where your data lives, what it costs to run, and what
happens when something fails. You build operational
knowledge that no managed cloud service can give you.
</p>
<p>
This does not mean rejecting cloud entirely. It means being
intentional. Use the cloud where the economics clearly favor
it. Build and operate everything else yourself. The goal is
a team that could reconstruct its own stack from scratch if
it had to — because a team capable of that is a team that
actually understands what it has built.
</p>
<hr />
<p>
The best infrastructure is not the most convenient. It is
the most understood. Tools that demand something from you —
configuration, learning, patience — are the tools that make
you better over time. Convenience is a ceiling. Complexity,
embraced deliberately, is a floor that keeps getting higher.
</p>
</article>
<footer>
<a href="mailto:me@samantha42.xyz"
>me@samantha42.xyz</a
>
<p class="footer-copy">&copy; 2026 — All rights reserved</p>
</footer>
</div>
</body>
</html>
+97
View File
@@ -0,0 +1,97 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Samantha Vero Friis</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500&family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap"
rel="stylesheet" />
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
<link rel="stylesheet" href="/static/output.css"/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=DM+Mono:wght@300;400;500&display=swap" rel="stylesheet" />
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
</head>
<body class="theme-root day " id="themeRoot">
<div class="login-wrap">
<div class="login-card">
<div class="card-header">
<a style="text-decoration: underline; font-size: 13px; color: #85B7EB; cursor: pointer; font-family: 'DM Mono', monospace;" onclick="closelogin()">← back</a>
<p class="brand" style="margin-top: 12px;">samantha<span>42</span>.xyz</p>
<p class="brand-sub">Secure Portal</p>
</div>
<div class="card-body">
<div id="login-error"></div>
<form
hx-post="/login"
hx-target="#login-error"
hx-swap="innerHTML"
hx-indicator="#login-spinner"
hx-on::after-request="if(event.detail.successful && event.detail.xhr.status===200 && !event.detail.xhr.getResponseHeader('HX-Redirect')) {}"
>
<div class="field-group">
<label class="field-label" for="password">Password</label>
<input
class="field-input"
type="password"
id="password"
name="password"
placeholder="••••••••"
autocomplete="current-password"
required
/>
</div>
<div class="field-group">
<label class="field-label" for="totp">2FA Code</label>
<input
class="field-input"
type="text"
id="totp"
name="totp"
placeholder="123456"
inputmode="numeric"
maxlength="6"
autocomplete="one-time-code"
required
/>
</div>
<button class="submit-btn" type="submit">Continue →</button>
<p class="htmx-indicator" id="login-spinner">// verifying...</p>
</form>
</div>
</div>
</div>
<style>
.login-wrap { z-index: 10; min-width: 50%; min-height: 520px; display: flex; align-items: center; justify-content: center; padding: 2rem 0; }
.login-card { background: white; border: 0.5px solid var(--color-border-tertiary); border-radius: 8px; width: 100%; max-width: 400px; overflow: hidden; }
.card-header { background: #0C447C; padding: 2rem 2rem 1.5rem; }
.brand { font-family: 'DM Serif Display', serif; font-size: 22px; color: #E6F1FB; letter-spacing: -0.5px; margin: 0 0 4px; }
.brand span { font-style: italic; color: #85B7EB; }
.brand-sub { font-family: 'DM Mono', monospace; font-size: 11px; color: #378ADD; letter-spacing: 2px; text-transform: uppercase; margin: 0; }
.card-body { padding: 1.75rem 2rem; }
.field-group { margin-bottom: 1rem; }
.field-label { font-family: 'DM Mono', monospace; font-size: 11px; color: var(--color-text-secondary); letter-spacing: 1px; text-transform: uppercase; display: block; margin-bottom: 6px; }
.field-input { width: 100%; box-sizing: border-box; font-family: 'DM Mono', monospace; font-size: 14px; padding: 10px 12px; border-radius: 6px; border: 0.5px solid var(--color-border-tertiary); background: var(--color-background-secondary); color: var(--color-text-primary); transition: border-color 0.2s, box-shadow 0.2s; }
.field-input:focus { outline: none; border-color: #378ADD; box-shadow: 0 0 0 3px rgba(55,138,221,0.15); }
.submit-btn { width: 100%; padding: 11px; background: #185FA5; color: #E6F1FB; border: none; border-radius: 6px; font-family: 'DM Mono', monospace; font-size: 13px; letter-spacing: 1px; cursor: pointer; transition: background 0.2s, transform 0.1s; margin-top: 0.5rem; }
.submit-btn:hover { background: #0C447C; }
.submit-btn:active { transform: scale(0.98); }
.htmx-indicator { opacity: 0; transition: opacity 0.2s; font-family: 'DM Mono', monospace; font-size: 11px; color: var(--color-text-secondary); text-align: center; margin-top: 8px; }
.htmx-request .htmx-indicator { opacity: 1; }
.htmx-request .submit-btn { background: #0C447C; cursor: not-allowed; pointer-events: none; }
.error-banner { font-family: 'DM Mono', monospace; font-size: 11px; color: #A32D2D; background: #FCEBEB; border: 0.5px solid #F09595; border-radius: 6px; padding: 8px 12px; margin-bottom: 1rem; }
</style>
</body>
</html>
+56
View File
@@ -0,0 +1,56 @@
{{define "nav"}}
{{end}}
{{define "body"}}
<section id="automation">
<div class="page-header">
<h2 class="page-title">Technical Tools & Automation</h2>
<span class="page-sub">engineering/programing</span>
</div>
<div class="exp-grid">
<p class="exp-body">
The task here is simple: to reduce work, it's a simple investment. The choice of picking the correct
bottleneck is as important as the tool itself, as this gives it use. If it's never used, it never creates
value. The same goes for uptime, it's important that stability is almost perfect, as downtime undermines
the utility of having the tool. Remember that making and using a tool also creates debt for future
maintenance of the tool.
<br>
To adhere to this, I often choose Golang or Python as they are cheap to maintain. Golang has good
stability because it is easy to code and compile checks give early error detection. A word of caution on
AI generated tools: while they are cheap to make, the maintenance debt grows exponentially, as the code is
often poorly understood by the team and difficult to reason about over time.
</p>
<div class="exp-card" >
<div class="exp-header">
<span class="exp-title">Portfolio Engine</span>
</div>
<p class="exp-body">A lightweight portfolio and financial data backend written in Go. Tracks companies,
currencies, revenue reports, and trades across configurable time periods — replacing spreadsheets with a
proper database, REST API, and interactive shell.</p>
<span class="exp-title">Roadmap</span>
<ul style="padding: 20px;">
<li class="exp-body">Price update endpoint</li>
<li class="exp-body">Multi-currency conversion</li>
<li class="exp-body">Frontend dashboard</li>
<li class="exp-body">Shares outstanding history database table</li>
<li class="exp-body">Earnings per share (EPS) based on shares owned</li>
<li class="exp-body">First-In, First-Out (FIFO) realized gains</li>
<li class="exp-body">Dividends earned and related taxes</li>
<li class="exp-body">Total tax obligations</li>
</ul>
<a class="outlink" href="https://git.samantha42.xyz/samantha/Portfolio-Engine">Git repo</a>
</div>
<div class="exp-card">
<div class="exp-header">
<span class="exp-title">Accounting Management</span>
</div>
<p class="exp-body">
A lightweight, SQL-backed, web-based CLI system designed to track assets and liabilities. The goal is to
provide a simple, fast, and scalable financial tracking tool that can grow from personal use to more
advanced scenarios.</p>
</div>
</div>
</section>
{{end}}
+7
View File
@@ -0,0 +1,7 @@
{{define "nav"}}
{{end}}
{{define "body"}}
{{end}}
+144
View File
@@ -0,0 +1,144 @@
{{define "nav"}}
<div>
<p class="sidebar-label">navigate</p>
<div class="sidebar-links">
<a href="#about">about</a>
<a href="#experience">experience</a>
<a href="#education">education</a>
<a href="#interests">interests</a>
</div>
</div>
<div>
<p class="sidebar-label">pages</p>
<div class="sidebar-links">
<a href="/automation">automation</a>
<a href="/research">research</a>
<a href="/bookkeeping">bookkeeping</a>
<a href="/interests">interests</a>
</div>
</div>
<div>
<p class="sidebar-label">skills</p>
<div class="skill-list">
<div class="skill-item">Excel</div>
<div class="skill-item">Python</div>
<div class="skill-item">SQL</div>
<div class="skill-item">Go</div>
<div class="skill-item">Git</div>
<div class="skill-item">Linux</div>
</div>
</div>
<div>
<p class="sidebar-label">location</p>
<div style="font-family:var(--ff-mono);font-size:0.75rem;color:var(--muted);">Denmark<br>Aalborg Øst, 9220</div>
</div>
<div>
<p class="sidebar-label">languages</p>
<div class="lang-row">
<span class="lang-badge">Danish</span>
<span class="lang-badge">English</span>
</div>
</div>
{{end}}
{{define "body"}}
<section class="hero">
<p class="hero-eyebrow">About Me · 2026</p>
<h1>Samantha<br><span>Vero Friis</span>
</h1>
<p class="hero-bio">Bachelor's student in Business Administration seeking an entry-level analyst or
finance-related role while studying. Strong numerical skills, advanced Excel, and experience in company, risk,
and financial analysis. Analytical, detail-oriented, and efficiency-focused.</p>
<div class="hero-meta">
<span>
<div hx-get="/status" hx-trigger="load" hx-swap="outerHTML"></div>
<span class="exp-tag">samantha42.xyz</span>
<span class="exp-tag">me@samantha42.xyz</span>
</div>
</section>
<section id="experience">
<div class="section-head">
<h2>experience</h2>
</div>
<div class="exp-grid">
<div class="exp-card link" onclick="window.location.href='/research'">
<div class="exp-header">
<span class="exp-title">Independent Equity Research</span>
<span class="exp-tag">finance</span>
</div>
<p class="exp-body">Fundamental analysis using SEC filings (10-K, 10-Q) and earnings reports. Assess
profitability, liquidity, risk factors, and long-term business sustainability. Handle incomplete or
low-transparency information through structured cross-validation.</p>
</div>
<div class="exp-card link" onclick="window.location.href='/bookkeeping'">
<div class="exp-header">
<span class="exp-title">Bookkeeping &amp; Administration</span>
<span class="exp-tag">Record keeping</span>
</div>
<p class="exp-body">Excel to In house Bookkeeping tooling and backups and safe sql databse for larger scale. made to better comply to lawmakers in KLACQ ApS(small self owned company) currently selling SaaS products. <a href="https:klacq.eu">klacq website</a> </p>
</div>
<div class="exp-card link" onclick="window.location.href='/automation'">
<div class="exp-header">
<span class="exp-title">Technical Tools &amp; Automation</span>
<span class="exp-tag">programing</span>
</div>
<p class="exp-body">Build lightweight backend tools to automate repetitive analytical and operational tasks.
Python for data processing, validation, and workflow automation. Lightweight online services in Go,
focusing on speed and reliability.</p>
</div>
</div>
</section>
<section id="education">
<div class="section-head">
<h2>education</h2>
</div>
<div class="edu-grid">
<div class="edu-card">
<div class="edu-year">2025 — present</div>
<div>
<div class="edu-degree">Bachelor of Economics &amp; Business Administration (HA)</div>
<div class="edu-inst">Aalborg University · Expected 2028</div>
</div>
</div>
<div class="edu-card">
<div class="edu-year">2024 — 2025
<br>
<span class="edu-tag tag-dropped">discontinued</span>
</div>
<div class="edu-body">
<div class="edu-degree">Mathematics-Technology (BSc)</div>
<div class="edu-inst">Aalborg University · 1 semester</div>
</div>
</div>
<div class="edu-card">
<div class="edu-year">2020 — 2023</div>
<div>
<div class="edu-degree">HTX Technical Upper Secondary Education</div>
<div class="edu-inst">Viden Gymnasier</div>
<div class="edu-detail">Mathematics A, Biology B · Study project: Mathematics &amp; Technology</div>
</div>
</div>
</div>
</section>
<section id="interests">
<div class="section-head">
<h2>interests</h2>
</div>
<div class="exp-grid">
<div class="exp-card link" onclick="window.location.href='/interests'">
<div class="exp-header">
<span class="exp-title">Personal Free Time Interest</span>
<span class="exp-tag">Personal</span>
</div>
<p class="exp-body">Movies, computers, PC gaming and oard games</p>
</div>
</div>
</section>
{{end}}
+139
View File
@@ -0,0 +1,139 @@
{{define "nav"}}
<div>
<p class="sidebar-label">pages</p>
<div class="sidebar-links">
<a href="/automation">automation</a>
<a href="/research">research</a>
<a href="/bookkeeping">bookkeeping</a>
</div>
</div>
<div>
<p class="sidebar-label">subjects</p>
<div class="skill-list">
<div class="skill-item">movies</div>
<div class="skill-item">games</div>
<div class="skill-item">linux</div>
<div class="skill-item">board games</div>
</div>
</div>
{{end}}
{{define "body"}}
<section id="interests">
<div id="interests-body" >
<span class="matrix-hidden" id="matrix-hidden">
Boy: Do not try and bend the spoon. That's impossible. Instead... only try to realize the truth.
<br>
Neo: What truth?
<br>
Boy: There is no spoon.
</span>
<div class="section-head">
<h2>Movie/Show Top list</h2>
</div>
<div class="toplist-group">
<h3>Top 5 Movies</h3>
<div class="toplist-body">
<div class="poster-grid">
<div class="poster-item posterhover" onclick="bladerunnerClick()">
<img src="/static/posters/bladerunner2049.jpg" alt="Blade Runner 2049">
<p>Blade Runner 2049</p>
</div>
<div class="poster-item posterhover" onclick="cureClick()">
<img src="/static/posters/AcureForWellness.jpg" alt="A Cure of Wellness">
<p>A Cure of Wellness</p>
</div>
<div class="poster-item posterhover" onclick="matrixClick()">
<img src="/static/posters/matrix.jpg" alt="matrix">
<p>The Matrix</p>
</div>
<div class="poster-item posterhover" onclick="draculaClick()">
<img src="/static/posters/Dracula.webp" alt="Dracula: A Love Tale">
<p>Dracula: A Love Tale</p>
</div>
<div class="poster-item posterhover" onclick="dragonTypewriterClick()">
<img src="/static/posters/TheGirlwiththeDragonTattoo.webp" alt="Girl with a Dragon Tattoos">
<p>Girl with a Dragon Tattoo</p>
</div>
</div>
</div>
<div class="toplist-group">
<h3>Top 5 Shows</h3>
<div class="toplist-body">
<div class="poster-grid">
<div class="poster-item">
<img src="/static/posters/foundation.jpg" alt="foundation">
<p>Foundation</p>
</div>
<div class="poster-item">
<img src="/static/posters/Chernobyl.webp" alt="Chernobyl">
<p>Chernobyl</p>
</div>
<div class="poster-item">
<img src="/static/posters/Breaking Bad.webp" alt="Breaking Bad">
<p>Breaking Bad</p>
</div>
<div class="poster-item">
<img src="/static/posters/Avatar.webp" alt="Avatar: The Last Airbender">
<p>Avatar: The Last Airbender</p>
</div>
<div class="poster-item">
<img src="/static/posters/Adolescence.webp" alt="Adolescence">
<p>Adolescence</p>
</div>
</div>
</div>
</div>
<div class="section-head">
<h2>Lunix rice</h2>
</div>
<div style="display: flex; align-items: center; gap: 1rem;">
<p class="exp-body">I love my custom Linux setup. I use Hyprland, which automatically arranges app windows
on my screen so everything stays organized without dragging things around. I also use Quickshell, which
lets me customize menus, shortcuts, and system controls to work exactly how I want.
My system is based on Arch Linux or Manjaro Linux, which are versions of Linux that give you more
control and customization than a typical computer setup. The result is a clean, fast, and super
efficient desktop thats hard to go back from.
<a class="outlink" href="https://git.samantha42.xyz/samantha/dotfiles">Git repo</a>
</p>
<img src="/static/images/rice.png" style="height: 200px; align-self: center;">
</div>
<div class="section-head">
<h2>programing</h2>
</div>
<div style="display: flex; align-items: center; gap: 1rem;">
<img src="/static/images/program.png" style="height: 350px;" alt="program">
<div>
<p class="exp-body">
Programming can be a task, but for me it's a source of joy. This is something I do as a hobby, and I
don't use AI much. It's about having fun and making a fool of yourself. It's not about how good it is,
in this case, I'd rather just listen to music and drink a cup of tea. I enjoy the struggle to debug, the
beauty in understanding why a bug is there to begin with.
</p>
<br>
<p class="exp-body">
The text editor I use most is VS Code, but I love the Neovim setup as it enables me to code on a
low-level system without a desktop environment being necessary. I do most of my coding on my own
desktop, synced with Git to other systems, like the server that hosts this website, which I have built
myself. The creation of this website is more of a fun dumb activity for me... go click on one of the
movie posters and see.
</p>
</div>
</div>
<br>
<div class="section-head">
<h2>Games</h2>
</div>
<p class="exp-body" >Game i will play in my free time to wind down.</p>
<div hx-get="/static/components/games.html" hx-trigger="load" hx-swap="outerHTML">
<span class="spinner">Loading stats…</span>
</div>
</div>
</section>
{{end}}
+72
View File
@@ -0,0 +1,72 @@
{{define "nav"}}
<div>
<p class="sidebar-label">subjects</p>
<div class="skill-list">
<div class="skill-item">opinion</div>
<div class="skill-item">due diligence</div>
<div class="skill-item">performance</div>
</div>
</div>
{{end}}
{{define "body"}}
<section id="research">
<div class="page-header">
<h2 class="page-title">Equity Research</h2>
</div>
<div class="exp-card">
<div class="exp-header">
<span class="exp-title">Story vs Fundamental value</span>
</div>
<p class="exp-body">
The truth I keep coming back to is simple: find value grounded in the fundamental numbers of a company.
<br>
My approach is rooted in fundamental analysis using SEC filings (10-K, 10-Q), earnings reports, profitability, liquidity, risk factors, and long-term business sustainability. In low-transparency situations I rely on structured cross-validation across sources.
<br>
A pattern I keep seeing is CEOs speaking just close enough to reality to generate a short-term price boost, then moving the goalposts when reality catches up. What companies state in filings and what they communicate to retail or even professional investors can be two very different stories. Guidance only needs to reach far enough into the future to say almost anything.
</p>
</div>
<br>
<div class="section-head">
<h2>Current stock analysis</h2>
</div>
<div class="exp-card link" onclick="window.open('/static/pdf/mstr.pdf', '_blank')">
<div class="exp-header">
<span class="exp-title">MSTR: Strategy<span style="color: red;"> (Sell)</span></span>
<span class="exp-tag">due diligence</span>
</div>
<p class="exp-body">
The company only owns btc by issuing debt and preferred stock offering.
<br>
The company is a ponzi scheme at its best.
</p>
</div>
<br>
<div class="exp-card link" onclick="window.open('/static/pdf/novo.pdf', '_blank')">
<div class="exp-header">
<span class="exp-title">Novo.B/NVO: Novo Nordisk A/S<span style="color: green"> (buy)</span></span>
<span class="exp-tag">due diligence</span>
</div>
<p class="exp-body">
The product sales of wieght loss drugs are highclass, pipeline is undervalued.
<br>
Pipeline and wegovy pil is not reconsiced correctly as high focus on expireation of current sold product. high pisimistic view of pipeline.
</p>
</div>
<br>
<div class="section-head">
<h2>Equity portfolio performance</h2>
</div>
<div class="exp-card">
<div hx-get="/static/components/eq.html" hx-trigger="load" hx-swap="outerHTML">
<span class="spinner">Loading chart...</span>
</div>
</div>
</section>
{{end}}
+236
View File
@@ -0,0 +1,236 @@
<style>
html {
background: #000;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
#matrix-wrap {
background: #000;
min-height: 1200px;
width: 100%;
border-radius: var(--border-radius-lg);
overflow: hidden;
position: relative;
font-family: var(--font-mono);
}
canvas#rain {
display: block;
width: 100%;
height: 100%;
position: absolute;
top: 0; left: 0;
}
#terminal {
position: relative;
z-index: 2;
padding: 2rem 2.5rem;
min-height: 600px;
display: flex;
flex-direction: column;
}
#boot-lines {
flex: 1;
}
.boot-line {
font-size: 13px;
color: #00ff41;
line-height: 1.8;
opacity: 0;
white-space: pre;
}
.boot-line.dim { color: #00aa2b; }
.boot-line.bright { color: #a3ffb4; font-weight: 500; }
.boot-line.warn { color: #ffcc00; }
#prompt-area {
margin-top: 1.5rem;
opacity: 0;
transition: opacity 0.5s;
}
#prompt-area.visible { opacity: 1; }
#input-row {
display: flex;
align-items: center;
gap: 8px;
}
#prompt-label {
color: #00ff41;
font-size: 14px;
white-space: nowrap;
}
#cmd-input {
background: transparent;
border: none;
border-bottom: 1px solid #00ff4166;
border-radius: 0;
color: #00ff41;
font-family: var(--font-mono);
font-size: 14px;
outline: none;
flex: 1;
padding: 2px 4px;
caret-color: #00ff41;
}
#cmd-input:focus { border-bottom-color: #00ff41; }
#output {
margin-top: 1rem;
font-size: 13px;
color: #00ff41;
line-height: 1.9;
min-height: 60px;
white-space: pre-wrap;
}
.o-dim { color: #00aa2b; }
.o-bright { color: #a3ffb4; }
.o-warn { color: #ffcc00; }
.o-pink { color: #ff79c6; }
</style>
<div id="matrix-wrap">
<canvas id="rain"></canvas>
<div id="terminal">
<div id="boot-lines"></div>
<div id="prompt-area">
<div id="input-row">
<span id="prompt-label">samantha42@secret:~$</span>
<input id="cmd-input" type="text" autocomplete="off" spellcheck="false" placeholder="type 'help'" aria-label="Terminal input" />
</div>
<div id="output"></div>
</div>
</div>
</div>
<script>
const canvas = document.getElementById('rain');
const ctx = canvas.getContext('2d');
const wrap = document.getElementById('matrix-wrap');
function resizeCanvas() {
canvas.width = wrap.offsetWidth;
canvas.height = wrap.offsetHeight;
}
resizeCanvas();
const chars = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン0123456789ABCDEF@#$%&*';
const fontSize = 14;
let cols, drops;
function initDrops() {
cols = Math.floor(canvas.width / fontSize);
drops = Array(cols).fill(1);
}
initDrops();
function drawRain() {
ctx.fillStyle = 'rgba(0,0,0,0.05)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.font = fontSize + 'px monospace';
for (let i = 0; i < drops.length; i++) {
const bright = Math.random() > 0.95;
ctx.fillStyle = bright ? '#a3ffb4' : '#00ff41';
ctx.globalAlpha = bright ? 1 : 0.7;
const ch = chars[Math.floor(Math.random() * chars.length)];
ctx.fillText(ch, i * fontSize, drops[i] * fontSize);
ctx.globalAlpha = 1;
if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) drops[i] = 0;
drops[i]++;
}
}
setInterval(drawRain, 50);
const bootSequence = [
{ text: 'SAMANTHA42 OS v4.2.0 — initializing...', cls: 'dim', delay: 200 },
{ text: 'Loading kernel modules... [OK]', cls: 'dim', delay: 400 },
{ text: 'Mounting secret filesystem... [OK]', cls: 'dim', delay: 600 },
{ text: 'Verifying identity... [OK]', cls: 'dim', delay: 900 },
{ text: '> ACCESS GRANTED ', cls: 'bright', delay: 1200 },
{ text: '', cls: '', delay: 1350 },
{ text: 'Welcome back, curious one. ', cls: 'warn', delay: 1500 },
{ text: 'You found the secret page. Nice work. ', cls: '', delay: 1700 },
{ text: '', cls: '', delay: 1850 },
{ text: "Type 'help' to see what's hidden here.", cls: 'dim', delay: 2000 },
];
const bootEl = document.getElementById('boot-lines');
const promptArea = document.getElementById('prompt-area');
bootSequence.forEach(({ text, cls, delay }) => {
setTimeout(() => {
const line = document.createElement('div');
line.className = 'boot-line' + (cls ? ' ' + cls : '');
line.textContent = text;
bootEl.appendChild(line);
requestAnimationFrame(() => { line.style.opacity = '1'; });
}, delay);
});
setTimeout(() => { promptArea.classList.add('visible'); }, 2200);
const commands = {
help: () => `<span class="o-bright">available commands:</span>
<span class="o-dim"> whoami </span>— the real story
<span class="o-dim"> skills </span>— the unofficial skill list
<span class="o-dim"> facts </span>— things my CV won't tell you
<span class="o-dim"> crush </span>— current hyperfixations
<span class="o-dim"> 42 </span>— the answer
<span class="o-dim"> clear </span>— clear the terminal`,
whoami: () => `<span class="o-bright">samantha</span> — numberphile, programmer, overthinker.
loves: movies, pixel-perfect layouts or fun design, staying up too late debugging.
hates: Comic Sans and broken padding.
found here by: <span class="o-pink">being curious</span> (good trait btw)`,
skills: () => `<span class="o-bright">UNOFFICIAL SKILL TREE</span>
<span class="o-dim"> ████████████████████</span> pretending to understand the error
<span class="o-dim"> █████████████████░░░</span> googling things I definitely know
<span class="o-dim"> ████████████████████</span> making things pretty at 2am
<span class="o-dim"> █████████░░░░░░░░░░░</span> replying to emails promptly
<span class="o-dim"> ████████████████████</span> convincing clients the bug is a feature`,
facts: () => `<span class="o-bright">fun facts not on
→ 42 is my favourite number (obviously)
→ I debug best when listening to lo-fi`,
crush: () => `<span class="o-bright">current hyperfixations:</span>
<span class="o-pink"> ♡</span> linux ricing
<span class="o-pink"> ♡</span> Japanese Katakana typography
<span class="o-pink"> ♡</span> number go up. But Fuck btc, lol
<span class="o-pink"> ♡</span> praying to loard gaben. If you know, you know
<span class="o-pink"> ♡</span> building useless website pages, clearly`,
42: () => `<span class="o-bright">"The Answer to the Ultimate Question of Life,
the Universe, and Everything."</span>
— Deep Thought, after 7.5 million years
also: my lucky number. also: this website's name.
<span class="o-dim">don't ask what the question is. nobody knows.</span>`,
clear: () => '__clear__',
};
const input = document.getElementById('cmd-input');
const output = document.getElementById('output');
input.addEventListener('keydown', (e) => {
if (e.key !== 'Enter') return;
const cmd = input.value.trim().toLowerCase();
input.value = '';
if (!cmd) return;
if (commands[cmd]) {
const result = commands[cmd]();
if (result === '__clear__') {
output.innerHTML = '';
} else {
output.innerHTML = `<span class="o-dim">$ ${cmd}</span>\n` + result + '\n\n';
}
} else {
output.innerHTML = `<span class="o-dim">$ ${cmd}</span>\n<span class="o-warn">command not found: ${cmd}</span>\ntype <span class="o-bright">help</span> to see available commands\n\n`;
}
});
input.addEventListener('focus', () => {});
setTimeout(() => { input.focus(); }, 2300);
</script>
+72
View File
@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Samantha Vero Friis</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500&family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap"
rel="stylesheet" />
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
<link rel="stylesheet" href="/static/output.css"/>
</head>
<body class="theme-root day " id="themeRoot">
<nav>
<a class="nav-logo" href="/#">samantha_vero_friis</a> {{if .Sub}}<a class="nav-logo sub">{{.Title}}</a> {{end}}
</nav>
<div class="shell">
<aside>
<div class="row">
<img id="day" src="/static/icons/lovesun.png" width="30px" class="hidden" onclick="makeday()" >
<img id="night" src="/static/icons/moon.png" width="30px" onclick="makenight()" >
</div>
{{if .Sub}}
<div>
<div class="sidebar-links back">
<a href="/">BACK</a>
</div>
</div>
{{end}}
{{template "nav" .}}
<div style="flex: 1;"></div>
</aside>
<main id="{{.Title}}">
{{template "body" .}}
</main>
</div>
<footer>
<div class="footer-contact">
<a href="mailto:me@samantha42.xyz">me@samantha42.xyz</a>
</div>
<div class="footer-copy">© 2026 — all rights reserved</div>
</footer>
<script>
console.log("%c👀 hi there!", "font-size:20px")
function makeday() {
themeRoot.classList.remove('night');
const day = document.getElementById('day');
const night = document.getElementById('night');
day.classList.add("hidden");
night.classList.remove("hidden");
}
function makenight() {
themeRoot.classList.add('night');
const day = document.getElementById('day');
const night = document.getElementById('night');
day.classList.remove("hidden");
night.classList.add("hidden");
}
</script>
</body>
</html>
+50
View File
@@ -0,0 +1,50 @@
<section id="automation">
<div class="page-header">
<h2 class="page-title">Technical Tools & Automation</h2>
<span class="page-sub">engineering/programing</span>
</div>
<div class="exp-grid">
<p class="exp-body">
The task here is simple: to reduce work, it's a simple investment. The choice of picking the correct
bottleneck is as important as the tool itself, as this gives it use. If it's never used, it never creates
value. The same goes for uptime, it's important that stability is almost perfect, as downtime undermines
the utility of having the tool. Remember that making and using a tool also creates debt for future
maintenance of the tool.
<br>
To adhere to this, I often choose Golang or Python as they are cheap to maintain. Golang has good
stability because it is easy to code and compile checks give early error detection. A word of caution on
AI generated tools: while they are cheap to make, the maintenance debt grows exponentially, as the code is
often poorly understood by the team and difficult to reason about over time.
</p>
<div class="exp-card" >
<div class="exp-header">
<span class="exp-title">Portfolio Engine</span>
</div>
<p class="exp-body">A lightweight portfolio and financial data backend written in Go. Tracks companies,
currencies, revenue reports, and trades across configurable time periods — replacing spreadsheets with a
proper database, REST API, and interactive shell.</p>
<span class="exp-title">Roadmap</span>
<ul style="padding: 20px;">
<li class="exp-body">Price update endpoint</li>
<li class="exp-body">Multi-currency conversion</li>
<li class="exp-body">Frontend dashboard</li>
<li class="exp-body">Shares outstanding history database table</li>
<li class="exp-body">Earnings per share (EPS) based on shares owned</li>
<li class="exp-body">First-In, First-Out (FIFO) realized gains</li>
<li class="exp-body">Dividends earned and related taxes</li>
<li class="exp-body">Total tax obligations</li>
</ul>
<a class="outlink" href="https://git.samantha42.xyz/samantha/Portfolio-Engine">Git repo</a>
</div>
<div class="exp-card">
<div class="exp-header">
<span class="exp-title">Accounting Management</span>
</div>
<p class="exp-body">
A lightweight, SQL-backed, web-based CLI system designed to track assets and liabilities. The goal is to
provide a simple, fast, and scalable financial tracking tool that can grow from personal use to more
advanced scenarios.</p>
</div>
</div>
</section>