Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions admin/src/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4347,6 +4347,39 @@ function SystemEditor({
{serviceStatus.checkedAt && <small className="system-note">Checked {new Date(serviceStatus.checkedAt).toLocaleString()}</small>}
</section>

<section className="panel admin-pipeline-panel">
<PanelHeader eyebrow="CONTENT PIPELINE" title="Private publishing path" />
<p className="helper">The local admin stays private. Content is created here, passed to the Tailscale API, then synchronized through the service that owns each type of data.</p>

<div className="admin-pipeline-map" aria-label="Admin publishing architecture">
<div className="pipeline-node primary">
<small>01</small>
<strong>Admin UI</strong>
<span>local/private</span>
</div>
<div className="pipeline-arrow">→</div>
<div className="pipeline-node">
<small>02</small>
<strong>Tailscale API</strong>
<span>:8787</span>
</div>
<div className="pipeline-arrow">→</div>
<div className="pipeline-services">
<div className="pipeline-node"><small>03A</small><strong>Google Drive</strong><span>documents + library</span></div>
<div className="pipeline-node"><small>03B</small><strong>Cloudflare R2</strong><span>image / video / audio</span></div>
<div className="pipeline-node"><small>03C</small><strong>GitHub App</strong><span>commit published JSON</span></div>
<div className="pipeline-node"><small>03D</small><strong>Vercel API</strong><span>public chatbot/read API</span></div>
</div>
</div>

<div className="pipeline-note-grid">
<span><b>Writeups:</b> document editor → GitHub App</span>
<span><b>Media:</b> editor → R2</span>
<span><b>Source files:</b> editor → Drive</span>
<span><b>Public AI:</b> Vercel first, Tailscale backup</span>
</div>
</section>

<section className="panel">
<PanelHeader eyebrow="PUBLIC AI" title="Chatbot smoke test" />
<p className="helper">Sends one real request to the online Vercel chatbot using the current portfolio content.</p>
Expand Down
94 changes: 94 additions & 0 deletions admin/src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -433,3 +433,97 @@ button{color:inherit}
grid-column:auto;
}
}


/* Private admin integration map */
.admin-pipeline-panel{
grid-column:1 / -1;
}

.admin-pipeline-map{
display:grid;
grid-template-columns:minmax(130px,.8fr) auto minmax(150px,.9fr) auto minmax(0,2fr);
gap:10px;
align-items:center;
margin-top:16px;
}

.pipeline-services{
display:grid;
grid-template-columns:repeat(2,minmax(0,1fr));
gap:8px;
}

.pipeline-node{
display:grid;
gap:4px;
min-height:88px;
padding:13px;
border:1px solid var(--line);
border-radius:11px;
background:#0f0c13;
}

.pipeline-node.primary{
border-color:rgba(242,168,212,.28);
background:linear-gradient(145deg,rgba(242,168,212,.08),transparent),#0f0c13;
}

.pipeline-node small{
color:#786b7e;
font:700 8px/1.2 monospace;
}

.pipeline-node strong{
color:#f0e6f2;
font-size:12px;
}

.pipeline-node span{
color:#8f8296;
font-size:9px;
line-height:1.4;
}

.pipeline-arrow{
color:#5bcefa;
font:700 18px/1 monospace;
}

.pipeline-note-grid{
display:grid;
grid-template-columns:repeat(2,minmax(0,1fr));
gap:7px;
margin-top:12px;
}

.pipeline-note-grid span{
padding:8px 10px;
border:1px solid var(--line);
border-radius:8px;
background:#0e0b12;
color:#918596;
font-size:9px;
}

.pipeline-note-grid b{
color:#d8cbdc;
}

@media(max-width:900px){
.admin-pipeline-map{
grid-template-columns:1fr;
}

.pipeline-arrow{
transform:rotate(90deg);
justify-self:center;
}
}

@media(max-width:620px){
.pipeline-services,
.pipeline-note-grid{
grid-template-columns:1fr;
}
}
60 changes: 50 additions & 10 deletions client/blog-astro/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ const comics = Array.isArray(webcomicIndex?.comics)
? webcomicIndex.comics
: [];

const featured = [
{ title: 'Digital Forensics', category: 'DFIR', description: 'From evidence collection to timelines and incident reconstruction.', icon: '◈', href: '#ctf-writeups' },
{ title: 'Computer Systems', category: 'SYSTEMS', description: 'Networks, operating systems and how things work beneath the interface.', icon: '⌘', href: '#systems-notes' },
{ title: 'Creative Side Quests', category: 'WEBCOMIC', description: 'A softer illustrated corner for stories and tiny experiments.', icon: '✿', href: '#webcomic' },
];

const recent = [
...blogPosts.map((item) => ({
...item,
Expand Down Expand Up @@ -283,13 +289,19 @@ const hrefFor = (item, kind) => {
<div class="card-image">
{
item.__placeholder ? (
<div class="empty-art">
<span>
ฅ^•ﻌ•^ฅ
</span>
<small>
awaiting signal
</small>
<div class="empty-art image-placeholder">
<img
src={image}
alt=""
width="960"
height="540"
loading="lazy"
decoding="async"
/>
<div class="empty-caption">
<span>ฅ^•ﻌ•^ฅ</span>
<small>awaiting signal</small>
</div>
</div>
) : (
<img
Expand Down Expand Up @@ -372,13 +384,41 @@ const hrefFor = (item, kind) => {
</div>
</section>

<section class="featured-section" id="systems-lab" aria-label="Systems and security playground">
<div class="featured-heading">
<span class="featured-kicker">02 / SYSTEMS + SECURITY PLAYGROUND</span>
<h2>Pick a rabbit hole.</h2>
<p>Explore practical computer systems, cybersecurity, and creative side quests while the published notebook grows.</p>
</div>
<div class="featured-grid">
{featured.map((item) => (
<a class="featured-card" href={item.href}>
<span class="featured-icon" aria-hidden="true">{item.icon}</span>
<small>{item.category}</small>
<h3>{item.title}</h3>
<p>{item.description}</p>
<span class="featured-link">Explore collection ↗</span>
</a>
))}
</div>
</section>

<section class="welcome-note" aria-label="Publishing status">
<span class="welcome-signal" aria-hidden="true"></span>
<div>
<strong>The notebook is open.</strong>
<p>Published posts and writeups are still empty right now. New entries will appear automatically after they're published through the private content studio.</p>
</div>
<a href="#webcomic">Meet the cat ↗</a>
</section>

<section
id="ctf-writeups"
class="shelf"
>
<header class="shelf-head">
<div>
<small>02 / SECURITY LAB</small>
<small>03 / SECURITY LAB</small>
<h2>CTF writeups</h2>
<p>
Challenge notes, evidence,
Expand Down Expand Up @@ -507,7 +547,7 @@ const hrefFor = (item, kind) => {
>
<header class="shelf-head">
<div>
<small>03 / POLYMATH NOTEBOOK</small>
<small>04 / POLYMATH NOTEBOOK</small>
<h2>Systems & field notes</h2>
<p>
Computer systems, software,
Expand Down Expand Up @@ -633,7 +673,7 @@ const hrefFor = (item, kind) => {
>
<header class="shelf-head">
<div>
<small>04 / SIDE QUEST</small>
<small>05 / SIDE QUEST</small>
<h2>Webcomic</h2>
<p>
Small illustrated stories,
Expand Down
69 changes: 69 additions & 0 deletions client/blog-astro/src/styles/blog.css
Original file line number Diff line number Diff line change
Expand Up @@ -698,3 +698,72 @@ main{
transition:none!important;
}
}


/* Editorial entrance: real navigation before the first article is published */
.featured-section{padding:55px 0 10px}
.featured-heading{max-width:680px;margin-bottom:22px}
.featured-kicker{font-size:10px;font-weight:800;letter-spacing:.16em;color:var(--blue)}
.featured-heading h2{margin:9px 0;color:var(--text);font-size:clamp(30px,4vw,50px);letter-spacing:-.04em}
.featured-heading p{color:var(--muted);font-size:13px;line-height:1.65}
.featured-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:15px}
.featured-card{position:relative;display:flex;flex-direction:column;min-height:235px;padding:23px;border:1px solid var(--line);border-radius:18px;background:linear-gradient(145deg,rgba(91,206,250,.06),transparent 55%),var(--panel);text-decoration:none;transition:transform .2s,border-color .2s}
.featured-card:nth-child(2){background:linear-gradient(145deg,rgba(242,168,212,.08),transparent 55%),var(--panel)}
.featured-card:nth-child(3){background:linear-gradient(145deg,rgba(255,255,255,.05),transparent 55%),var(--panel)}
.featured-card:hover,.featured-card:focus-visible{transform:translateY(-4px);border-color:rgba(91,206,250,.4);outline:none}
.featured-icon{display:grid;place-items:center;width:39px;height:39px;margin-bottom:15px;border:1px solid rgba(91,206,250,.2);border-radius:12px;color:var(--pink);font-size:24px}
.featured-card small{color:var(--blue);font-size:9px;letter-spacing:.12em}
.featured-card h3{margin:8px 0;color:var(--text);font-size:21px}
.featured-card p{margin:0 0 18px;color:var(--muted);font-size:12px;line-height:1.65}
.featured-link{margin-top:auto;color:var(--pink);font-size:11px;font-weight:700}
.welcome-note{display:flex;align-items:center;gap:15px;margin-top:25px;padding:20px 23px;border:1px solid rgba(242,168,212,.16);border-radius:16px;background:rgba(242,168,212,.035)}
.welcome-signal{flex:none;width:10px;height:10px;border-radius:50%;background:var(--pink);box-shadow:0 0 15px rgba(242,168,212,.35)}
.welcome-note strong{font-size:13px;color:var(--text)}
.welcome-note p{margin:5px 0 0;color:var(--muted);font-size:11px;line-height:1.6}
.welcome-note>a{flex:none;margin-left:auto;color:var(--blue);text-decoration:none;font-size:11px;font-weight:700}
@media(max-width:760px){.featured-grid{grid-template-columns:1fr}.featured-card{min-height:190px}.welcome-note{align-items:flex-start;flex-wrap:wrap}.welcome-note>a{margin-left:25px}}


/* Finish placeholder cards as image-led cards rather than empty markup. */
.empty-art.image-placeholder{
position:relative;
display:block;
background:#0d0b10;
}

.empty-art.image-placeholder>img{
width:100%;
height:100%;
object-fit:cover;
filter:saturate(.72) brightness(.58);
opacity:.9;
}

.empty-caption{
position:absolute;
inset:0;
display:grid;
place-content:center;
gap:6px;
text-align:center;
background:linear-gradient(0deg,rgba(10,8,12,.7),rgba(10,8,12,.08));
}

.empty-caption span{
color:#f0d9e8;
font-size:25px;
text-shadow:0 5px 20px rgba(0,0,0,.4);
}

.empty-caption small{
color:#d6c9d8;
font-size:7px;
letter-spacing:.11em;
text-transform:uppercase;
}

.shelf,
.featured-section{
content-visibility:auto;
contain-intrinsic-size:720px;
}
Loading