Starter Template
Drop this as your page skeleton. It already has the palette, fonts, and a starry background. Everything below slots inside <main>.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>My Codex</title>
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;500&family=Cormorant+Garamond:ital,wght@0,300;1,300&family=Instrument+Sans:wght@400;500&display=swap" rel="stylesheet">
<style>
:root{ --void:#060A1F; --plum:#1E1442; --violet:#7A5CFF; --lavender:#B69CFF; --silver:#C8D0E6; --bright:#E8ECF8; --line:rgba(182,156,255,0.18); }
*{box-sizing:border-box} body{margin:0;background:var(--void);color:var(--silver);font-family:'Instrument Sans',system-ui,sans-serif}
.wrap{max-width:1100px;margin:0 auto;padding:28px 20px}
h1,h2{font-family:'Cinzel',serif;color:var(--bright)}
.glass{background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.08);border-radius:18px;backdrop-filter:blur(12px)}
</style>
</head>
<body>
<div class="wrap">
<h1>My Ethereal Page</h1>
<p style="font-family:'Cormorant Garamond',serif;font-size:19px;opacity:.9">Paste components below here — they will automatically match your palette.</p>
</div>
</body>
</html>:root and one .glass style — then paste as many sections as you want. Duplicate a card grid, swap the text, done.Accordions
Perfect for lore, Titan entries, or any long text you want scannable. Two flavors: pure HTML (no JS) and animated glass.
Terralynth — The Stonefather Foundation
The grounding force. Click to close — no JavaScript needed. The browser handles open/close.
Serisyn — The Veilwalker
Death • Transformation • Quiet Between Songs — introspective, unafraid of change.
<!-- ACCORDION — PURE HTML (copy this) -->
<style>
details.ethe{ background:rgba(255,255,255,0.05); border:1px solid rgba(255,255,255,0.08);
border-radius:14px; padding:12px 14px; margin:10px 0; }
details.ethe summary{ cursor:pointer; font-family:'Cinzel',serif; color:#E8ECF8; list-style:none; }
details.ethe summary::-webkit-details-marker{ display:none }
details.ethe p{ margin-top:10px; font-family:'Cormorant Garamond',serif; font-size:16px; line-height:1.6 }
</style>
<details class="ethe" open>
<summary>Terralynth — The Stonefather</summary>
<p>The grounding force of existence. No JS needed — the browser toggles it.</p>
</details>
<details class="ethe">
<summary>Serisyn — The Veilwalker</summary>
<p>Death • Transformation • Quiet Between Songs.</p>
</details><!-- ACCORDION — ANIMATED GLASS -->
<style>
.acc{ border:1px solid rgba(255,255,255,0.08); border-radius:14px; overflow:hidden; background:rgba(255,255,255,0.04) }
.acc-item{ border-bottom:1px solid rgba(255,255,255,0.06) } .acc-item:last-child{border:0}
.acc-q{ width:100%; text-align:left; padding:14px 16px; background:none; border:0; color:#E8ECF8;
font-family:'Cinzel',serif; display:flex; justify-content:space-between; cursor:pointer }
.acc-a{ display:none; padding:0 16px 14px; opacity:.8 } .acc-item.open .acc-a{ display:block }
</style>
<div class="acc" id="myAcc">
<div class="acc-item open">
<button class="acc-q" onclick="toggle(this)">Lysera — First Light <span>−</span></button>
<div class="acc-a">Light • Pulse • Memory • Healing.</div>
</div>
<div class="acc-item">
<button class="acc-q" onclick="toggle(this)">Arenox — Shadowed Hunger <span>+</span></button>
<div class="acc-a">Not evil — the raw truth of wanting.</div>
</div>
</div>
<script>
function toggle(btn){
const item = btn.parentElement;
const open = item.classList.contains('open');
// close others (remove this loop to allow many open)
document.querySelectorAll('#myAcc .acc-item').forEach(i=>{i.classList.remove('open'); i.querySelector('span').textContent='+';});
if(!open){ item.classList.add('open'); btn.querySelector('span').textContent='−'; }
}
</script>Cards & Grids
Grids are your superpower. Swap repeat(3,1fr) to 2 or 1 for mobile — we already did.
Virelia
Dawn • Beginnings
She arrived like a question the world needed to ask.
Celestira
Stars • Wonder
Bright-eyed, shimmering — written in a single exhale.
Alezion
Order • Balance
Each step aligned with the world's resonant pulse.
<!-- 3-COL GRID — COPY -->
<style>
.grid3{ display:grid; grid-template-columns:repeat(3,1fr); gap:14px }
@media(max-width:760px){ .grid3{ grid-template-columns:1fr } }
.card{ background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.08);
border-radius:16px; padding:16px; transition:.25s }
.card:hover{ transform:translateY(-3px); border-color:rgba(182,156,255,0.28); box-shadow:0 12px 30px rgba(0,0,0,0.3) }
</style>
<div class="grid3">
<div class="card"><h3>Virelia</h3><p>The First Question — dawn, renewal.</p></div>
<div class="card"><h3>Celestira</h3><p>Star-Dreamer — wonder, astral.</p></div>
<div class="card"><h3>Alezion</h3><p>Balanced Step — order, intention.</p></div>
</div>Terralynth & Serisyn
Foundation × Transformation — the union that birthed the Six. Make this card span two rows.
Try grid-column / grid-row to reshape
Sylvara
Life rises again from sorrow.
Syric + Aria
Fate and chance — thread and spark.
<!-- BENTO GRID -->
<style>
.bento{ display:grid; grid-template-columns:1.6fr 1fr; gap:14px }
@media(max-width:760px){ .bento{ grid-template-columns:1fr } }
</style>
<div class="bento">
<div class="card" style="grid-row:span 2">Big feature</div>
<div class="card">Small A</div>
<div class="card">Small B</div>
</div>
<!-- tip: use grid-column:span 2 or grid-row:span 2 to make any card bigger -->Buttons & Links
Use link buttons as signposts — keep verbs clear: Enter, Reveal, Listen, Remember.
<!-- BUTTONS — COPY -->
<style>
.btn{ display:inline-flex; align-items:center; gap:8px; padding:11px 18px; border-radius:100px;
font-size:11px; letter-spacing:.14em; text-transform:uppercase; text-decoration:none; cursor:pointer }
.btn-primary{ background:linear-gradient(135deg,#7A5CFF,#B69CFF); color:white; border:0; box-shadow:0 8px 20px rgba(122,92,255,0.3) }
.btn-ghost{ background:rgba(255,255,255,0.06); color:#E8ECF8; border:1px solid rgba(255,255,255,0.12) }
.btn-star{ background:rgba(255,216,138,0.12); color:#FFD88A; border:1px solid rgba(255,216,138,0.25) }
</style>
<a class="btn btn-primary" href="#titans">✦ Enter the Falls</a>
<a class="btn btn-ghost" href="#memory">Listen to the Quiet</a>
<a class="btn btn-star" href="#">♫ Play the Song</a>Glowing Stars & Music Notes
Subtle motion keeps an ethereal page alive — never flashy, always drifting like dust in moonlight.
<!-- FLOATING NOTES — CSS ONLY -->
<style>
.note{ position:absolute; color:rgba(182,156,255,0.6); font-family:'Cormorant Garamond',serif; font-size:22px;
animation:drift 6s ease-in-out infinite; }
@keyframes drift{ 0%,100%{ transform:translateY(0) rotate(-5deg); opacity:.6 } 50%{ transform:translateY(-14px) rotate(5deg); opacity:.95 } }
</style>
<div style="position:relative; height:160px">
<span class="note" style="left:12%; top:30%">♫</span>
<span class="note" style="left:58%; top:50%; animation-delay:.7s">♪</span>
<span class="note" style="left:78%; top:20%; animation-delay:1.4s">♩</span>
</div><!-- STARFIELD — add canvas + this script -->
<canvas id="stars" style="position:fixed;inset:0;z-index:-1"></canvas>
<script>
const c=document.getElementById('stars'), x=c.getContext('2d');
function size(){ c.width=innerWidth; c.height=innerHeight; }
size(); addEventListener('resize',size);
const stars=[...Array(140)].map(()=>({x:Math.random()*innerWidth, y:Math.random()*innerHeight, r:Math.random()*1.2+.2, o:Math.random()*.7+.2}));
(function draw(){
x.clearRect(0,0,innerWidth,innerHeight);
stars.forEach(s=>{
x.globalAlpha=s.o + Math.sin(Date.now()*0.002 + s.x)*0.15;
x.fillStyle=Math.random()>.88?'#FFD88A':Math.random()>.85?'#B69CFF':'white';
x.beginPath(); x.arc(s.x,s.y,s.r,0,Math.PI*2); x.fill();
});
if(Math.random()<0.007){ // shooting star
const sx=Math.random()*innerWidth, sy=Math.random()*180;
x.strokeStyle='rgba(255,255,255,0.9)'; x.globalAlpha=0.9; x.beginPath(); x.moveTo(sx,sy); x.lineTo(sx+70,sy+18); x.stroke();
}
requestAnimationFrame(draw);
})();
</script>Click the orb — it’s just a div with a radial gradient.
<!-- ORB — CSS ONLY -->
<style>
.orb{ width:140px; height:140px; border-radius:50%;
background:radial-gradient(circle at 30% 30%, white 0%, #B69CFF 45%, #1E1442 75%);
box-shadow:0 0 30px rgba(182,156,255,0.5), inset 0 0 18px rgba(255,255,255,0.25);
transition:.35s; cursor:pointer }
.orb:hover{ transform:scale(1.04); box-shadow:0 0 44px rgba(182,156,255,0.7) }
</style>
<div class="orb" onclick="this.animate([{transform:'scale(1)'},{transform:'scale(1.08)'},{transform:'scale(1)'}],{duration:600})"></div>Long Narrative Helpers
Break walls of text with drop caps, pull quotes, timelines, and clamps — readers stay inside the song.
They did not arise from creation; creation arose from them. Their nature is older than form, older than memory, older even than the first song that shaped Atheria.
<!-- DROP CAP + PULL QUOTE -->
<style>
.dropcap::first-letter{ font-family:'Cinzel Decorative',serif; float:left; font-size:42px; line-height:.8; padding:6px 10px 0 0; color:#B69CFF }
.quote{ border-left:2px solid #B69CFF; padding-left:16px; font-family:'Cormorant Garamond',serif; font-style:italic; font-size:18px; color:#E8ECF8 }
.rule{ height:1px; background:linear-gradient(90deg, transparent, rgba(182,156,255,0.28), transparent); margin:18px 0 }
</style>
<p class="dropcap">They did not arise from creation; creation arose from them...</p>
<div class="rule"></div>
<blockquote class="quote">“Lysera passes her light to Vaessa before unraveling.”</blockquote><!-- TIMELINE -->
<style>
.tl{ position:relative; padding-left:22px }
.tl::before{ content:''; position:absolute; left:6px; top:4px; bottom:4px; width:1px; background:linear-gradient(180deg,#B69CFF, transparent) }
.tl-item{ position:relative; margin-bottom:14px; padding:12px 14px; border-radius:14px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.06) }
.tl-item::before{ content:''; position:absolute; left:-18px; top:18px; width:9px; height:9px; border-radius:50%; background:#B69CFF; box-shadow:0 0 10px rgba(182,156,255,0.6) }
</style>
<div class="tl">
<div class="tl-item"><b>First Quickening — Virelia</b><div>Curiosity sparks dawn.</div></div>
<div class="tl-item"><b>Breath — Celestira</b><div>An exhale writes the stars.</div></div>
</div><!-- TABS -->
<style>
.tabs{ display:flex; gap:8px; flex-wrap:wrap }
.tab{ padding:8px 14px; border-radius:100px; border:1px solid rgba(255,255,255,0.12); background:rgba(255,255,255,0.04); cursor:pointer }
.tab.active{ background:linear-gradient(135deg,#7A5CFF,#B69CFF); color:white; border-color:transparent }
.panel{ display:none; margin-top:12px; padding:16px; border-radius:14px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.06) }
.panel.active{ display:block }
</style>
<div class="tabs">
<button class="tab active" onclick="showTab(0)">Titans</button>
<button class="tab" onclick="showTab(1)">The Six</button>
</div>
<div class="panel active">Titans content...</div>
<div class="panel">The Six content...</div>
<script>
function showTab(i){
document.querySelectorAll('.tab').forEach((b,idx)=>b.classList.toggle('active', idx===i));
document.querySelectorAll('.panel').forEach((p,idx)=>p.classList.toggle('active', idx===i));
}
</script>Atheria was sung into existence — and it is kept in tune by those who remember, reflect, and feel for one another. Vaessa witnesses so that Arenox may be integrated. Lysera's pulse becomes Vaessa's stillness. The Titans' ancient chord still hums beneath the Six's harmony. Collective memory is not storage — it is care made cosmic. When three moons align, the world remembers itself, and every thread trembles back into tune.
<!-- READ-MORE CLAMP -->
<style>
.clamp{ display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden }
.fade{ position:absolute; left:0; right:0; bottom:0; height:48px; background:linear-gradient(180deg, transparent, #060A1F) }
</style>
<div style="position:relative">
<p id="text" class="clamp">Long paragraph here...</p>
<div id="fade" class="fade"></div>
</div>
<button onclick="toggle()">Read more</button>
<script>
let open=false;
function toggle(){
open=!open;
document.getElementById('text').classList.toggle('clamp', !open);
document.getElementById('fade').style.display = open ? 'none' : 'block';
}
</script>Carousels
Vanilla JS, swipe-ready structure, dots + arrows, no library. Swap the slides for art, maps, or moon phases.
Replace each .carousel-slide with <img src="..."> — it just works. Add more dots to match slides.
<!-- CAROUSEL — COPY EVERYTHING -->
<style>
.carousel{ position:relative; overflow:hidden; border-radius:16px; border:1px solid rgba(255,255,255,0.08); background:rgba(255,255,255,0.04) }
.track{ display:flex; transition:transform .45s ease }
.slide{ min-width:100%; height:260px; display:grid; place-items:center; font-family:'Cinzel',serif; color:white }
.slide img{ width:100%; height:100%; object-fit:cover; display:block }
.dots{ position:absolute; left:50%; bottom:12px; transform:translateX(-50%); display:flex; gap:8px }
.dot{ width:8px; height:8px; border-radius:50%; background:rgba(255,255,255,0.35); border:1px solid rgba(255,255,255,0.4); cursor:pointer }
.dot.active{ background:white }
.arrow{ position:absolute; top:50%; transform:translateY(-50%); width:34px; height:34px; border-radius:50%;
display:grid; place-items:center; background:rgba(6,10,31,0.55); border:1px solid rgba(255,255,255,0.12); color:white; cursor:pointer }
.arrow.left{ left:10px } .arrow.right{ right:10px }
</style>
<div class="carousel" id="myCarousel">
<div class="track" id="myTrack">
<div class="slide" style="background:linear-gradient(135deg,#1E1442,#7A5CFF)">Slide 1 — replace with <img></div>
<div class="slide" style="background:linear-gradient(135deg,#0B102E,#B69CFF)">Slide 2</div>
<div class="slide" style="background:linear-gradient(135deg,#1A2A4A,#FFD88A)">Slide 3</div>
</div>
<button class="arrow left" onclick="go(-1)">‹</button>
<button class="arrow right" onclick="go(1)">›</button>
<div class="dots" id="myDots">
<span class="dot active" onclick="jump(0)"></span><span class="dot" onclick="jump(1)"></span><span class="dot" onclick="jump(2)"></span>
</div>
</div>
<script>
let idx=0, n=3; // n = number of slides
function update(){
document.getElementById('myTrack').style.transform = `translateX(-${idx*100}%)`;
document.querySelectorAll('#myDots .dot').forEach((d,i)=>d.classList.toggle('active', i===idx));
}
function go(d){ idx=(idx+d+n)%n; update(); }
function jump(i){ idx=i; update(); }
setInterval(()=>go(1), 4200); // auto-play — remove to disable
</script>
<!-- To use images: replace each .slide inner with <img src="your-image.jpg" alt=""> -->Extras — Modal & TOC
Two tiny utilities that make a codex feel like an experience, not a document.
<!-- MODAL -->
<style>
.overlay{ position:fixed; inset:0; display:none; place-items:center; background:rgba(6,10,31,0.68); backdrop-filter:blur(8px); z-index:50 }
.overlay.open{ display:grid }
.modal{ width:min(560px, 92vw); background:rgba(14,18,44,0.98); border:1px solid rgba(182,156,255,0.22);
border-radius:18px; padding:20px; box-shadow:0 18px 50px rgba(0,0,0,0.45) }
</style>
<button onclick="openModal()">Open</button>
<div class="overlay" id="overlay" onclick="if(event.target===this) closeModal()">
<div class="modal">
<h3 style="font-family:'Cinzel',serif; color:#E8ECF8">The Canticle of Unraveling</h3>
<p style="font-family:'Cormorant Garamond',serif; margin-top:8px">Lysera passes her light to Vaessa...</p>
<button onclick="closeModal()" style="margin-top:14px">Close</button>
</div>
</div>
<script>
function openModal(){ document.getElementById('overlay').classList.add('open'); }
function closeModal(){ document.getElementById('overlay').classList.remove('open'); }
addEventListener('keydown', e=>{ if(e.key==='Escape') closeModal(); });
</script>Combo Playground
Tick the blocks you want, then copy the combined skeleton. This is how you build a whole page without starting from scratch.
<div class="wrap">. Change the :root colors once and the whole page re-themes.You don't need to learn everything at once — just copy one block, change the words, and see it live. Then add the next.