DIY — Project Sharing Landing
A photo-first landing page for MAKELOG, a fictional project-sharing community, built with plain HTML, CSS and vanilla JavaScript. It pairs a featured hero card with a channel navigation bar, a dense grid of sixteen project cards with hover quick-view overlays, a dark contests band with live countdown timers, a trending makers row, and a newsletter band with inline validation. Every thumbnail is drawn with CSS gradients, so nothing loads from the network.
MCP
Code
:root{
--bg:#fffdfa;
--primary:#e8552d;
--primary-d:#c8401c;
--primary-soft:#ffe7de;
--teal:#0f8a8a;
--teal-d:#0b6a6a;
--yellow:#ffd166;
--ink:#22201e;
--ink-2:#4a4642;
--muted:#7c766f;
--white:#ffffff;
--line:rgba(34,32,30,0.12);
--line-2:rgba(34,32,30,0.22);
--ok:#2f9e6f;
--danger:#d4503e;
--ch-circuits:#6b4fd8;
--ch-workshop:#c8701f;
--ch-craft:#e8552d;
--ch-cooking:#0f8a8a;
--ch-living:#3d8a3d;
--r-sm:8px; --r-md:14px; --r-lg:22px;
--sh-1:0 1px 2px rgba(34,32,30,.06), 0 4px 14px rgba(34,32,30,.06);
--sh-2:0 10px 30px rgba(34,32,30,.14);
}
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
margin:0;background:var(--bg);color:var(--ink);
font-family:Inter,system-ui,sans-serif;line-height:1.5;
-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
}
h1,h2,h3{margin:0;line-height:1.15;letter-spacing:-.02em}
p{margin:0}
button,input{font:inherit;color:inherit}
.mono{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:.78rem;letter-spacing:-.01em}
.muted{color:var(--muted)}
.muted-inv{color:rgba(255,253,250,.62)}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.wrap{width:min(1220px,100% - 2.5rem);margin-inline:auto}
:focus-visible{outline:3px solid var(--teal);outline-offset:2px;border-radius:6px}
.skip{position:absolute;left:-999px;top:0;z-index:99;background:var(--ink);color:#fff;padding:.6rem 1rem;border-radius:0 0 var(--r-sm) 0}
.skip:focus{left:0}
/* ---------- buttons ---------- */
.btn{
border:1px solid transparent;border-radius:999px;padding:.62rem 1.15rem;
font-weight:700;font-size:.88rem;cursor:pointer;background:none;
transition:transform .16s ease, background .18s ease, box-shadow .18s ease, border-color .18s;
}
.btn:active{transform:translateY(1px) scale(.985)}
.btn.primary{background:var(--primary);color:#fff;box-shadow:0 4px 0 var(--primary-d)}
.btn.primary:hover{background:var(--primary-d);box-shadow:0 3px 0 #a53214}
.btn.ghost{border-color:var(--line-2);background:var(--white);color:var(--ink)}
.btn.ghost:hover{border-color:var(--ink);background:#fff7f3}
.btn.ghost.inv{background:rgba(255,255,255,.14);color:#fff;border-color:rgba(255,255,255,.5);backdrop-filter:blur(4px)}
.btn.ghost.inv:hover{background:rgba(255,255,255,.26)}
.btn.ghost.inv[aria-pressed="true"]{background:var(--yellow);color:var(--ink);border-color:var(--yellow)}
.btn.sm{padding:.5rem .95rem;font-size:.82rem}
/* ---------- header ---------- */
.site{position:sticky;top:0;z-index:40;background:rgba(255,253,250,.94);backdrop-filter:blur(10px);border-bottom:1px solid var(--line)}
.head-in{display:flex;align-items:center;gap:1rem;padding:.85rem 0}
.brand{display:flex;align-items:center;gap:.55rem;text-decoration:none;color:var(--ink);flex:none}
.brand-mark svg{width:38px;height:38px;display:block}
.brand-word{font-weight:800;font-size:1.22rem;letter-spacing:-.045em}
.brand-word em{font-style:normal;color:var(--primary)}
.brand-word.sm{font-size:1rem}
.search{position:relative;flex:1;max-width:520px;display:flex;align-items:center}
.search-ico{position:absolute;left:.85rem;width:18px;height:18px;color:var(--muted)}
.search input{
width:100%;padding:.66rem 2.6rem;border:1px solid var(--line-2);border-radius:999px;
background:var(--white);font-size:.9rem;transition:border-color .18s, box-shadow .18s;
}
.search input:focus{outline:none;border-color:var(--primary);box-shadow:0 0 0 4px var(--primary-soft)}
.search kbd{position:absolute;right:.6rem;font-family:"JetBrains Mono",monospace;font-size:.72rem;color:var(--muted);border:1px solid var(--line-2);border-radius:5px;padding:.05rem .38rem;background:#faf7f2}
.head-actions{display:flex;align-items:center;gap:.5rem;margin-left:auto}
.avatar{width:38px;height:38px;border-radius:50%;border:2px solid var(--line-2);background:var(--teal);color:#fff;font-weight:700;font-size:.8rem;cursor:pointer;transition:transform .16s}
.avatar:hover{transform:scale(1.06)}
/* channels */
.channels{border-top:1px solid var(--line);background:var(--white)}
.chan-in{display:flex;align-items:center;gap:.2rem;overflow-x:auto;scrollbar-width:none;padding:.15rem 0}
.chan-in::-webkit-scrollbar{display:none}
.chan{
position:relative;border:0;background:none;cursor:pointer;white-space:nowrap;
padding:.75rem .9rem;font-weight:600;font-size:.9rem;color:var(--ink-2);
display:inline-flex;align-items:center;gap:.45rem;transition:color .16s;
}
.chan i{width:9px;height:9px;border-radius:50%;background:var(--c);display:inline-block}
.chan::after{content:"";position:absolute;left:.7rem;right:.7rem;bottom:0;height:3px;border-radius:3px 3px 0 0;background:var(--primary);transform:scaleX(0);transition:transform .22s cubic-bezier(.2,.8,.3,1)}
.chan:hover{color:var(--ink)}
.chan.is-on{color:var(--primary)}
.chan.is-on::after{transform:scaleX(1)}
.chan-count{margin-left:auto;font-family:"JetBrains Mono",monospace;font-size:.74rem;color:var(--muted);white-space:nowrap;padding-left:1rem}
/* ---------- hero ---------- */
.hero{display:grid;grid-template-columns:minmax(0,1.9fr) minmax(0,1fr);gap:1.25rem;padding:1.6rem 0 2.2rem}
.hero-card{
position:relative;border-radius:var(--r-lg);overflow:hidden;min-height:400px;
display:flex;align-items:flex-end;color:#fff;box-shadow:var(--sh-2);
transition:transform .25s ease;
}
.hero-card:hover{transform:translateY(-3px)}
.hero-card::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,rgba(20,14,10,0) 28%,rgba(20,14,10,.5) 58%,rgba(20,14,10,.92) 100%)}
.hero-body{position:relative;z-index:2;padding:1.9rem;max-width:62ch}
.hero-body h1{font-size:clamp(1.5rem,3.1vw,2.35rem);font-weight:800;margin:.55rem 0 .6rem;text-shadow:0 2px 18px rgba(0,0,0,.4)}
.hero-body p{color:rgba(255,255,255,.86);font-size:.96rem;max-width:56ch}
.hero-meta{display:flex;flex-wrap:wrap;align-items:center;gap:.6rem;margin:1rem 0 1.15rem;font-size:.84rem;color:rgba(255,255,255,.9)}
.hero-meta .mono{font-size:.76rem}
.dot{width:4px;height:4px;border-radius:50%;background:rgba(255,255,255,.5)}
.author{display:inline-flex;align-items:center;gap:.45rem}
.hero-cta{display:flex;gap:.6rem;flex-wrap:wrap}
.ribbon{
position:absolute;top:1.1rem;left:-2.6rem;z-index:3;transform:rotate(-38deg);
background:var(--yellow);color:var(--ink);font-weight:800;font-size:.75rem;letter-spacing:.09em;
text-transform:uppercase;padding:.4rem 3rem;box-shadow:0 2px 10px rgba(0,0,0,.25)
}
.ava{width:26px;height:26px;border-radius:50%;background:var(--a,var(--primary));color:#fff;display:grid;place-items:center;font-size:.66rem;font-weight:800;font-family:"JetBrains Mono",monospace;flex:none}
.chip-chan{display:inline-block;background:var(--c);color:#fff;font-size:.72rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;padding:.24rem .6rem;border-radius:999px}
.hero-side{display:flex;flex-direction:column;gap:1.25rem}
.side-card{background:var(--white);border:1px solid var(--line);border-radius:var(--r-lg);padding:1.15rem 1.2rem;box-shadow:var(--sh-1)}
.side-card.warm{background:linear-gradient(160deg,#fff6ea,#fffdfa)}
.side-card h2{font-size:.98rem;font-weight:800;margin-bottom:.75rem}
.stats{list-style:none;margin:0;padding:0;display:grid;gap:.6rem}
.stats li{display:flex;align-items:baseline;gap:.6rem;border-bottom:1px dashed var(--line);padding-bottom:.55rem}
.stats li:last-child{border:0;padding-bottom:0}
.stats b{font-size:1.15rem;color:var(--primary);font-family:"JetBrains Mono",monospace}
.stats span{font-size:.84rem;color:var(--ink-2)}
.makers{display:grid;gap:.35rem}
.maker{display:flex;align-items:center;gap:.6rem;padding:.45rem .5rem;border:0;background:none;border-radius:var(--r-sm);cursor:pointer;text-align:left;transition:background .16s}
.maker:hover{background:var(--primary-soft)}
.maker b{display:block;font-size:.86rem}
.maker i{font-style:normal;font-size:.75rem;color:var(--muted)}
/* ---------- section head ---------- */
.sec-head{display:flex;flex-wrap:wrap;align-items:center;gap:.8rem;margin-bottom:1.1rem}
.sec-head h2{font-size:1.35rem;font-weight:800}
.sec-head.inv h2{color:#fffdfa}
.sorts{margin-left:auto;display:flex;gap:.25rem;background:#f4efe7;border-radius:999px;padding:.22rem}
.sort{border:0;background:none;border-radius:999px;padding:.42rem .85rem;font-size:.82rem;font-weight:600;color:var(--ink-2);cursor:pointer;transition:background .16s,color .16s}
.sort:hover{color:var(--ink)}
.sort.is-on{background:var(--white);color:var(--primary);box-shadow:var(--sh-1)}
/* ---------- grid ---------- */
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(248px,1fr));gap:1.15rem;align-items:start;padding-bottom:2.6rem}
.card{
position:relative;background:var(--white);border:1px solid var(--line);border-radius:var(--r-md);
overflow:hidden;box-shadow:var(--sh-1);cursor:pointer;text-align:left;padding:0;width:100%;
opacity:0;transform:translateY(14px);
transition:transform .24s cubic-bezier(.2,.8,.3,1), box-shadow .24s, border-color .24s;
}
.card.in{opacity:1;transform:none}
.card:hover,.card:focus-visible{transform:translateY(-6px);box-shadow:var(--sh-2);border-color:var(--line-2)}
.card .strip{height:5px;background:var(--c)}
.card .art{position:relative;height:0;padding-bottom:66%;overflow:hidden}
.card.tall .art{padding-bottom:96%}
.card .quick{
position:absolute;inset:0;z-index:3;display:flex;flex-direction:column;justify-content:flex-end;gap:.5rem;
padding:.9rem;color:#fff;background:linear-gradient(180deg,rgba(20,14,10,0),rgba(20,14,10,.88));
opacity:0;transition:opacity .22s ease;
}
.card:hover .quick,.card:focus-visible .quick{opacity:1}
.quick p{font-size:.78rem;line-height:1.4;color:rgba(255,255,255,.9)}
.quick .qbtn{align-self:flex-start;background:var(--yellow);color:var(--ink);border:0;border-radius:999px;padding:.34rem .8rem;font-size:.76rem;font-weight:700}
.card-body{padding:.8rem .9rem 1rem}
.card-body h3{font-size:.98rem;font-weight:700;line-height:1.28}
.card-by{display:flex;align-items:center;gap:.4rem;margin-top:.55rem;font-size:.8rem;color:var(--ink-2)}
.card-by .ava{width:22px;height:22px;font-size:.6rem}
.card-foot{display:flex;align-items:center;gap:.8rem;margin-top:.6rem;padding-top:.6rem;border-top:1px dashed var(--line);color:var(--muted)}
.card-foot span{display:inline-flex;align-items:center;gap:.28rem}
.card-foot svg{width:14px;height:14px}
.badge{position:absolute;top:.6rem;right:.6rem;z-index:4;background:var(--yellow);color:var(--ink);font-size:.68rem;font-weight:800;letter-spacing:.05em;text-transform:uppercase;padding:.2rem .5rem;border-radius:999px;box-shadow:0 2px 8px rgba(0,0,0,.2)}
.empty{text-align:center;color:var(--muted);padding:2rem 0 3rem}
/* ---------- CSS illustrations ---------- */
.art{background:#1d2b33}
.art::before,.art::after{content:"";position:absolute;inset:0}
.art-lamp::before{background:
radial-gradient(circle at 50% 62%,rgba(255,209,102,.95),rgba(255,209,102,0) 34%),
radial-gradient(circle at 50% 62%,rgba(232,85,45,.55),rgba(232,85,45,0) 52%),
linear-gradient(160deg,#0f3b4a,#07202b);}
.art-lamp::after{background:
repeating-linear-gradient(90deg,rgba(255,255,255,.06) 0 2px,transparent 2px 26px),
radial-gradient(ellipse at 50% 96%,rgba(15,138,138,.55),transparent 60%);}
.art-a::before{background:linear-gradient(140deg,#3b2f6b,#6b4fd8 60%,#2a2350)}
.art-a::after{background:
radial-gradient(circle at 26% 34%,rgba(255,209,102,.9) 0 7px,transparent 8px),
radial-gradient(circle at 62% 58%,rgba(15,138,138,.9) 0 9px,transparent 10px),
repeating-linear-gradient(45deg,rgba(255,255,255,.09) 0 2px,transparent 2px 16px);}
.art-b::before{background:linear-gradient(150deg,#b3712a,#7b4517)}
.art-b::after{background:
repeating-linear-gradient(96deg,rgba(255,255,255,.11) 0 6px,rgba(0,0,0,.09) 6px 13px),
radial-gradient(ellipse at 70% 20%,rgba(255,209,102,.4),transparent 60%);}
.art-c::before{background:linear-gradient(135deg,#e8552d,#ffb08a)}
.art-c::after{background:
repeating-conic-gradient(from 0deg at 40% 45%,rgba(255,255,255,.22) 0 12deg,transparent 12deg 26deg),
radial-gradient(circle at 78% 78%,rgba(34,32,30,.28),transparent 55%);}
.art-d::before{background:linear-gradient(140deg,#0f8a8a,#0a4f52)}
.art-d::after{background:
radial-gradient(circle at 50% 55%,rgba(255,209,102,.85) 0 22%,transparent 23%),
radial-gradient(circle at 50% 55%,rgba(255,255,255,.25) 0 32%,transparent 33%);}
.art-e::before{background:linear-gradient(160deg,#2f6b3a,#123a20)}
.art-e::after{background:
radial-gradient(ellipse at 30% 70%,rgba(255,209,102,.35),transparent 55%),
repeating-linear-gradient(-40deg,rgba(255,255,255,.12) 0 3px,transparent 3px 22px);}
.art-f::before{background:linear-gradient(140deg,#5a4a8c,#1e1a33)}
.art-f::after{background:
linear-gradient(90deg,transparent 46%,rgba(255,209,102,.75) 46% 54%,transparent 54%),
radial-gradient(circle at 50% 22%,rgba(232,85,45,.6),transparent 55%);}
.art-g::before{background:linear-gradient(135deg,#d9a441,#8a5a1e)}
.art-g::after{background:
repeating-radial-gradient(circle at 50% 50%,rgba(255,255,255,.16) 0 4px,transparent 4px 15px);}
.art-h::before{background:linear-gradient(150deg,#0f8a8a,#ffd166)}
.art-h::after{background:
repeating-linear-gradient(0deg,rgba(34,32,30,.14) 0 8px,transparent 8px 18px),
radial-gradient(circle at 24% 26%,rgba(255,255,255,.5),transparent 40%);}
/* ---------- contests ---------- */
.contests{background:linear-gradient(165deg,#26231f,#141210);padding:2.8rem 0 3rem;color:#fffdfa;
background-image:linear-gradient(165deg,#26231f,#141210),repeating-linear-gradient(90deg,rgba(255,255,255,.03) 0 1px,transparent 1px 8px)}
.contest-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(270px,1fr));gap:1.1rem}
.contest{position:relative;background:rgba(255,253,250,.06);border:1px solid rgba(255,253,250,.14);border-radius:var(--r-lg);padding:1.3rem 1.2rem;overflow:hidden;transition:transform .2s,border-color .2s}
.contest:hover{transform:translateY(-4px);border-color:var(--yellow)}
.contest::before{content:"";position:absolute;inset:0 0 auto 0;height:6px}
.contest.c1::before{background:var(--ch-circuits)}
.contest.c2::before{background:var(--ch-workshop)}
.contest.c3::before{background:var(--ch-cooking)}
.c-tag{display:inline-block;font-size:.7rem;font-weight:800;letter-spacing:.09em;text-transform:uppercase;color:var(--yellow);margin-bottom:.4rem}
.contest h3{font-size:1.12rem;font-weight:800;margin-bottom:.5rem}
.prize{font-size:.86rem;color:rgba(255,253,250,.78)}
.prize b{color:var(--yellow)}
.entries{color:rgba(255,253,250,.55);margin-top:.35rem}
.count{display:flex;gap:.4rem;margin:.9rem 0 1rem}
.count span{background:rgba(0,0,0,.35);border:1px solid rgba(255,253,250,.14);border-radius:var(--r-sm);padding:.4rem .5rem;min-width:52px;text-align:center;font-family:"JetBrains Mono",monospace}
.count span b{display:block;font-size:1.05rem;color:#fffdfa;font-weight:700}
.count span i{font-style:normal;font-size:.6rem;letter-spacing:.1em;text-transform:uppercase;color:rgba(255,253,250,.55)}
/* ---------- trending ---------- */
.trend{padding:2.6rem 0}
.trend h2{font-size:1.35rem;font-weight:800;margin-bottom:1rem}
.trend-row{display:flex;gap:.7rem;overflow-x:auto;padding-bottom:.6rem;scrollbar-width:thin}
.tchip{flex:none;display:flex;align-items:center;gap:.6rem;background:var(--white);border:1px solid var(--line);border-radius:999px;padding:.45rem .95rem .45rem .45rem;cursor:pointer;transition:transform .16s,border-color .16s,background .16s}
.tchip:hover{transform:translateY(-2px);border-color:var(--primary);background:#fff8f5}
.tchip .ava{width:32px;height:32px;font-size:.7rem}
.tchip b{font-size:.85rem}
.tchip i{font-style:normal;display:block;font-size:.72rem;color:var(--muted);font-family:"JetBrains Mono",monospace}
.tchip[aria-pressed="true"]{background:var(--primary);border-color:var(--primary);color:#fff}
.tchip[aria-pressed="true"] i{color:rgba(255,255,255,.8)}
/* ---------- newsletter ---------- */
.news{background:linear-gradient(120deg,var(--primary),#ff8a5c);color:#fff;padding:2.6rem 0}
.news-in{display:flex;flex-wrap:wrap;align-items:center;gap:1.5rem;justify-content:space-between}
.news h2{font-size:1.5rem;font-weight:800}
.news p{color:rgba(255,255,255,.9);font-size:.92rem;margin-top:.35rem;max-width:46ch}
.news-form{position:relative;display:flex;gap:.5rem;flex-wrap:wrap}
.news-form input{padding:.72rem 1rem;border-radius:999px;border:2px solid transparent;min-width:260px;background:#fff;color:var(--ink)}
.news-form input:focus{outline:none;border-color:var(--yellow)}
.news-form input.bad{border-color:#5c1608;background:#fff0ec}
.err{position:absolute;top:100%;left:1rem;margin-top:.35rem;font-size:.78rem;font-weight:600;color:#3d0f05;min-height:1em}
.err.ok{color:#0c3f1f}
/* ---------- footer ---------- */
.site-foot{border-top:1px solid var(--line);padding:1.5rem 0;background:var(--white)}
.foot-in{display:flex;flex-wrap:wrap;align-items:center;gap:1rem}
.foot-in nav{display:flex;gap:1.1rem;flex-wrap:wrap}
.foot-in a{color:var(--ink-2);text-decoration:none;font-size:.85rem;font-weight:500}
.foot-in a:hover{color:var(--primary);text-decoration:underline}
.foot-in .mono{margin-left:auto}
/* ---------- toast ---------- */
.toast{
position:fixed;left:50%;bottom:1.5rem;transform:translate(-50%,140%);
background:var(--ink);color:#fffdfa;padding:.75rem 1.15rem;border-radius:999px;
font-size:.86rem;font-weight:600;box-shadow:var(--sh-2);z-index:80;
transition:transform .3s cubic-bezier(.2,.9,.3,1);max-width:90vw;text-align:center;
}
.toast.show{transform:translate(-50%,0)}
@media (prefers-reduced-motion:reduce){
*{animation:none!important;transition-duration:.01ms!important}
.card{opacity:1;transform:none}
}
@media (max-width:980px){
.hero{grid-template-columns:1fr}
.hero-card{min-height:330px}
}
@media (max-width:520px){
.wrap{width:min(100% - 1.6rem,100%)}
.head-in{flex-wrap:wrap;gap:.6rem}
.search{order:3;max-width:none;flex-basis:100%}
.head-actions{margin-left:auto;gap:.35rem}
.head-actions .ghost{display:none}
.hero-body{padding:1.2rem}
.hero-body h1{font-size:1.32rem}
.hero-meta{gap:.4rem;font-size:.78rem}
.grid{grid-template-columns:repeat(auto-fill,minmax(158px,1fr));gap:.8rem}
.card.tall .art{padding-bottom:76%}
.card-body{padding:.65rem .7rem .8rem}
.card-body h3{font-size:.86rem}
.sorts{margin-left:0;width:100%;justify-content:space-between}
.news-form input{min-width:0;flex:1}
.foot-in .mono{margin-left:0}
.ribbon{font-size:.68rem;padding:.35rem 3rem}
}(function () {
"use strict";
/* ---------------- toast ---------------- */
var toastEl = document.getElementById("toast");
var toastTimer;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
}, 2600);
}
document.addEventListener("click", function (e) {
var t = e.target.closest("[data-toast]");
if (t) toast(t.getAttribute("data-toast"));
});
/* ---------------- data ---------------- */
var PROJECTS = [
{ t: "Reflow hot plate from a thrifted griddle", a: "pixelbramble", ini: "PB", ch: "circuits", views: 41230, favs: 1880, art: "art-a", steps: 12, tall: true, badge: "Editor pick", q: "PID control, thermocouple mount, and a firmware profile for lead-free paste." },
{ t: "Folding workbench that hides in a doorway", a: "benchgrit", ini: "BG", ch: "workshop", views: 68410, favs: 3120, art: "art-b", steps: 9, q: "Two hinges, one torsion box, and a wall cleat rated for 90 kg." },
{ t: "Block-printed tea towels with a carved lino set", a: "kilnkate", ini: "KK", ch: "craft", views: 22740, favs: 1410, art: "art-c", steps: 7, q: "Registration jig keeps the second colour from drifting." },
{ t: "Cast iron focaccia with a 24-hour cold ferment", a: "sourdotto", ini: "SD", ch: "cooking", views: 91020, favs: 5240, art: "art-d", steps: 8, tall: true, badge: "Contest", q: "Hydration table, dimple technique, and a rosemary-brine finish." },
{ t: "Balcony rain barrel with a first-flush diverter", a: "greenjoist", ini: "GJ", ch: "living", views: 30110, favs: 1220, art: "art-e", steps: 11, q: "Mosquito screen, overflow routing, and a winter drain valve." },
{ t: "Nixie-style clock using seven-segment filaments", a: "marloreef", ini: "MR", ch: "circuits", views: 55340, favs: 2760, art: "art-f", steps: 15, q: "High-voltage warning notes plus a safer 12V filament alternative." },
{ t: "Hand-cut dovetail box from offcut walnut", a: "benchgrit", ini: "BG", ch: "workshop", views: 27890, favs: 1640, art: "art-g", steps: 10, tall: true, q: "Saw angle guide printed on card stock — no expensive jig." },
{ t: "Punch-needle wall hanging in three yarn weights", a: "kilnkate", ini: "KK", ch: "craft", views: 18420, favs: 990, art: "art-h", steps: 6, q: "Frame tensioning trick that stops monk's cloth from sagging." },
{ t: "Sheet-pan gnocchi with charred lemon", a: "sourdotto", ini: "SD", ch: "cooking", views: 47660, favs: 2980, art: "art-c", steps: 5, q: "One pan, 22 minutes, and a browning order that actually works." },
{ t: "Under-stair pull-out pantry on drawer slides", a: "greenjoist", ini: "GJ", ch: "living", views: 74210, favs: 4010, art: "art-b", steps: 14, badge: "Featured", q: "Load math for full-extension slides and a face-frame template." },
{ t: "Solar sensor node that sleeps for 11 months", a: "pixelbramble", ini: "PB", ch: "circuits", views: 36980, favs: 2210, art: "art-f", steps: 13, q: "Deep-sleep current budget measured with a shunt and a cheap meter." },
{ t: "Router sled to flatten a live-edge slab", a: "benchgrit", ini: "BG", ch: "workshop", views: 52400, favs: 2870, art: "art-g", steps: 8, tall: true, q: "Rails, shims, and a dust hood that keeps the shop breathable." },
{ t: "Marbled clay coasters fired in a tabletop kiln", a: "kilnkate", ini: "KK", ch: "craft", views: 20960, favs: 1180, art: "art-c", steps: 9, q: "Wedging pattern for clean veins and a cone-06 firing schedule." },
{ t: "Window herb ladder with wicking reservoirs", a: "greenjoist", ini: "GJ", ch: "living", views: 25330, favs: 1345, art: "art-e", steps: 7, q: "Capillary rope sizing so basil never sits in standing water." },
{ t: "Miso caramel popcorn in a stovetop kettle", a: "sourdotto", ini: "SD", ch: "cooking", views: 33770, favs: 2090, art: "art-d", steps: 6, q: "Sugar-stage temps and a stir rhythm that avoids scorching." },
{ t: "USB-C bench supply from a laptop brick", a: "marloreef", ini: "MR", ch: "circuits", views: 61250, favs: 3540, art: "art-a", steps: 16, badge: "Editor pick", q: "PD trigger board, fused output, and a printed banana-jack panel." }
];
var CH_COLOR = {
circuits: "var(--ch-circuits)",
workshop: "var(--ch-workshop)",
craft: "var(--ch-craft)",
cooking: "var(--ch-cooking)",
living: "var(--ch-living)"
};
var AVA = { circuits: "#6b4fd8", workshop: "#c8701f", craft: "#e8552d", cooking: "#0f8a8a", living: "#3d8a3d" };
function fmt(n) { return n.toLocaleString("en-US"); }
var EYE = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 12s3.6-6 10-6 10 6 10 6-3.6 6-10 6-10-6-10-6Z"/><circle cx="12" cy="12" r="2.6"/></svg>';
var STAR = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"><path d="m12 3 2.7 5.7 6.3.8-4.6 4.3 1.2 6.2L12 17l-5.6 3 1.2-6.2L3 9.5l6.3-.8Z"/></svg>';
var grid = document.getElementById("grid");
var emptyEl = document.getElementById("empty");
var countEl = document.getElementById("chanCount");
var state = { channel: "all", sort: "recent", query: "" };
function current() {
var list = PROJECTS.filter(function (p) {
if (state.channel !== "all" && p.ch !== state.channel) return false;
if (state.query) {
var hay = (p.t + " " + p.a + " " + p.ch).toLowerCase();
if (hay.indexOf(state.query) === -1) return false;
}
return true;
});
if (state.sort === "views") list.sort(function (a, b) { return b.views - a.views; });
else if (state.sort === "favs") list.sort(function (a, b) { return b.favs - a.favs; });
return list;
}
function render() {
var list = current();
grid.innerHTML = "";
emptyEl.hidden = list.length !== 0;
countEl.textContent = list.length + (list.length === 1 ? " project" : " projects");
list.forEach(function (p, i) {
var el = document.createElement("article");
el.className = "card" + (p.tall ? " tall" : "");
el.tabIndex = 0;
el.setAttribute("role", "button");
el.setAttribute("aria-label", p.t + " by " + p.a);
el.innerHTML =
'<div class="strip" style="--c:' + CH_COLOR[p.ch] + '"></div>' +
(p.badge ? '<span class="badge">' + p.badge + "</span>" : "") +
'<div class="art ' + p.art + '">' +
'<div class="quick"><p>' + p.q + "</p>" +
'<span class="qbtn">' + p.steps + " steps · quick view</span></div>" +
"</div>" +
'<div class="card-body">' +
"<h3>" + p.t + "</h3>" +
'<div class="card-by"><span class="ava" style="--a:' + AVA[p.ch] + '">' + p.ini + "</span>@" + p.a + "</div>" +
'<div class="card-foot mono">' +
"<span>" + EYE + fmt(p.views) + "</span>" +
"<span>" + STAR + fmt(p.favs) + "</span>" +
"</div>" +
"</div>";
function open() { toast("Opening “" + p.t + "” — " + p.steps + " steps."); }
el.addEventListener("click", open);
el.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") { e.preventDefault(); open(); }
});
grid.appendChild(el);
// staggered reveal
setTimeout(function () { el.classList.add("in"); }, Math.min(i, 14) * 45);
});
}
/* ---------------- channel tabs ---------------- */
Array.prototype.forEach.call(document.querySelectorAll(".chan"), function (b) {
b.addEventListener("click", function () {
document.querySelectorAll(".chan").forEach(function (o) {
o.classList.toggle("is-on", o === b);
o.setAttribute("aria-selected", o === b ? "true" : "false");
});
state.channel = b.getAttribute("data-channel");
render();
});
});
/* ---------------- sorts ---------------- */
Array.prototype.forEach.call(document.querySelectorAll(".sort"), function (b) {
b.addEventListener("click", function () {
document.querySelectorAll(".sort").forEach(function (o) { o.classList.toggle("is-on", o === b); });
state.sort = b.getAttribute("data-sort");
render();
});
});
/* ---------------- search ---------------- */
var q = document.getElementById("q");
var qTimer;
q.addEventListener("input", function () {
clearTimeout(qTimer);
qTimer = setTimeout(function () {
state.query = q.value.trim().toLowerCase();
render();
}, 140);
});
document.addEventListener("keydown", function (e) {
if (e.key === "/" && document.activeElement !== q) { e.preventDefault(); q.focus(); }
if (e.key === "Escape" && document.activeElement === q) { q.value = ""; state.query = ""; render(); q.blur(); }
});
/* ---------------- hero favorite ---------------- */
var fav = document.querySelector("[data-fav]");
fav.setAttribute("aria-pressed", "false");
fav.addEventListener("click", function () {
var on = fav.getAttribute("aria-pressed") === "true";
fav.setAttribute("aria-pressed", on ? "false" : "true");
fav.textContent = on ? "Favorite" : "Favorited";
toast(on ? "Removed from your locker." : "Added to your locker.");
});
/* ---------------- contest countdowns ---------------- */
var counters = Array.prototype.map.call(document.querySelectorAll(".count"), function (el) {
var parts = el.getAttribute("data-deadline").split(":").map(Number);
var secs = parts[0] * 86400 + parts[1] * 3600 + parts[2] * 60 + parts[3];
return { el: el, secs: secs };
});
function unit(v, label) {
return "<span><b>" + String(v).padStart(2, "0") + "</b><i>" + label + "</i></span>";
}
function tick() {
counters.forEach(function (c) {
if (c.secs > 0) c.secs -= 1;
var s = c.secs;
var d = Math.floor(s / 86400), h = Math.floor((s % 86400) / 3600),
m = Math.floor((s % 3600) / 60), sec = s % 60;
c.el.innerHTML = unit(d, "days") + unit(h, "hrs") + unit(m, "min") + unit(sec, "sec");
});
}
tick();
setInterval(tick, 1000);
/* ---------------- trending makers ---------------- */
var MAKERS = [
{ n: "marloreef", i: "MR", c: "#0f8a8a", s: "312k views" },
{ n: "benchgrit", i: "BG", c: "#c8701f", s: "268k views" },
{ n: "sourdotto", i: "SD", c: "#8a5a2b", s: "244k views" },
{ n: "kilnkate", i: "KK", c: "#e8552d", s: "191k views" },
{ n: "pixelbramble", i: "PB", c: "#6b4fd8", s: "177k views" },
{ n: "greenjoist", i: "GJ", c: "#3d8a3d", s: "160k views" },
{ n: "tinbending", i: "TB", c: "#4a4642", s: "142k views" },
{ n: "glassnoodle", i: "GN", c: "#b0407a", s: "128k views" }
];
var trendRow = document.getElementById("trendRow");
MAKERS.forEach(function (m) {
var b = document.createElement("button");
b.type = "button";
b.className = "tchip";
b.setAttribute("aria-pressed", "false");
b.innerHTML = '<span class="ava" style="--a:' + m.c + '">' + m.i + "</span><span><b>@" + m.n + "</b><i>" + m.s + "</i></span>";
b.addEventListener("click", function () {
var on = b.getAttribute("aria-pressed") === "true";
b.setAttribute("aria-pressed", on ? "false" : "true");
toast(on ? "Unfollowed @" + m.n + "." : "Following @" + m.n + ".");
});
trendRow.appendChild(b);
});
/* ---------------- newsletter validation ---------------- */
var form = document.getElementById("newsForm");
var email = document.getElementById("email");
var err = document.getElementById("emailErr");
var RE = /^[^\s@]+@[^\s@]+\.[a-z]{2,}$/i;
function validate(silent) {
var v = email.value.trim();
var msg = "";
if (!v) msg = "Enter an email so we know where to send it.";
else if (!RE.test(v)) msg = "That does not look like a valid address.";
err.classList.remove("ok");
err.textContent = silent && !v ? "" : msg;
email.classList.toggle("bad", !!msg && !(silent && !v));
email.setAttribute("aria-invalid", msg ? "true" : "false");
return !msg;
}
email.addEventListener("input", function () { validate(true); });
form.addEventListener("submit", function (e) {
e.preventDefault();
if (!validate(false)) { email.focus(); return; }
err.textContent = "You are in — first issue lands Thursday.";
err.classList.add("ok");
email.classList.remove("bad");
email.value = "";
toast("Subscribed to the MAKELOG weekly.");
});
render();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>MAKELOG — Share what you make</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;600;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip" href="#feed">Skip to projects</a>
<header class="site">
<div class="wrap head-in">
<a class="brand" href="#" aria-label="MAKELOG home">
<span class="brand-mark" aria-hidden="true">
<svg viewBox="0 0 40 40" role="presentation">
<rect x="2" y="2" width="36" height="36" rx="11" fill="var(--primary)"/>
<path d="M10 28V13l6.5 8L23 13v15" fill="none" stroke="#fffdfa" stroke-width="3.4" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="29.5" cy="26.5" r="3.1" fill="var(--yellow)"/>
</svg>
</span>
<span class="brand-word">MAKE<em>LOG</em></span>
</a>
<form class="search" role="search" onsubmit="return false">
<svg viewBox="0 0 24 24" aria-hidden="true" class="search-ico"><circle cx="11" cy="11" r="6.5" fill="none" stroke="currentColor" stroke-width="2"/><path d="M16 16l4.5 4.5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
<input id="q" type="search" placeholder="Search 214,880 projects…" aria-label="Search projects" autocomplete="off" />
<kbd>/</kbd>
</form>
<div class="head-actions">
<button class="btn ghost" type="button" data-toast="Saved projects live in your locker.">Locker</button>
<button class="btn primary" type="button" data-toast="Draft started — add your first step photo.">Publish</button>
<button class="avatar" type="button" aria-label="Account menu" data-toast="Signed in as @solderfern.">SF</button>
</div>
</div>
<nav class="channels" aria-label="Channels">
<div class="wrap chan-in" role="tablist">
<button class="chan is-on" role="tab" aria-selected="true" data-channel="all">All</button>
<button class="chan" role="tab" aria-selected="false" data-channel="circuits"><i style="--c:var(--ch-circuits)"></i>Circuits</button>
<button class="chan" role="tab" aria-selected="false" data-channel="workshop"><i style="--c:var(--ch-workshop)"></i>Workshop</button>
<button class="chan" role="tab" aria-selected="false" data-channel="craft"><i style="--c:var(--ch-craft)"></i>Craft</button>
<button class="chan" role="tab" aria-selected="false" data-channel="cooking"><i style="--c:var(--ch-cooking)"></i>Cooking</button>
<button class="chan" role="tab" aria-selected="false" data-channel="living"><i style="--c:var(--ch-living)"></i>Living</button>
<span class="chan-count" id="chanCount">14 projects</span>
</div>
</nav>
</header>
<main>
<!-- HERO -->
<section class="wrap hero" aria-labelledby="heroTitle">
<article class="hero-card art art-lamp" tabindex="0">
<span class="ribbon">Featured</span>
<div class="hero-body">
<span class="chip-chan" style="--c:var(--ch-circuits)">Circuits</span>
<h1 id="heroTitle">Tide Lamp: an ambient light that tracks the local tide table</h1>
<p>A 3D-printed diffuser over an addressable LED ring, driven by a tiny board that pulls tide data once an hour and eases the glow from low-water teal to high-water amber.</p>
<div class="hero-meta">
<span class="author"><span class="ava" style="--a:var(--teal)">MR</span> by <b>@marloreef</b></span>
<span class="dot"></span><span class="mono">18 steps</span>
<span class="dot"></span><span class="mono">92,410 views</span>
<span class="dot"></span><span class="mono">4,102 favorites</span>
</div>
<div class="hero-cta">
<button class="btn primary" type="button" data-toast="Opening the build — 18 steps, 41 photos.">Open build</button>
<button class="btn ghost inv" type="button" data-fav>Favorite</button>
</div>
</div>
</article>
<aside class="hero-side">
<div class="side-card">
<h2>This week on MAKELOG</h2>
<ul class="stats">
<li><b class="mono">1,284</b><span>projects posted</span></li>
<li><b class="mono">37,902</b><span>build notes</span></li>
<li><b class="mono">9</b><span>contests open</span></li>
</ul>
</div>
<div class="side-card warm">
<h2>Makers to follow</h2>
<div class="makers">
<button class="maker" type="button" data-toast="Following @kilnkate."><span class="ava" style="--a:var(--primary)">KK</span><span><b>@kilnkate</b><i>Craft · 61 builds</i></span></button>
<button class="maker" type="button" data-toast="Following @benchgrit."><span class="ava" style="--a:var(--teal)">BG</span><span><b>@benchgrit</b><i>Workshop · 44 builds</i></span></button>
<button class="maker" type="button" data-toast="Following @sourdotto."><span class="ava" style="--a:#8a5a2b">SD</span><span><b>@sourdotto</b><i>Cooking · 128 builds</i></span></button>
<button class="maker" type="button" data-toast="Following @pixelbramble."><span class="ava" style="--a:#6b4fd8">PB</span><span><b>@pixelbramble</b><i>Circuits · 73 builds</i></span></button>
</div>
</div>
</aside>
</section>
<!-- FEED -->
<section class="wrap" id="feed" aria-labelledby="feedTitle">
<div class="sec-head">
<h2 id="feedTitle">Fresh from the workbench</h2>
<div class="sorts" role="group" aria-label="Sort projects">
<button class="sort is-on" type="button" data-sort="recent">Recent</button>
<button class="sort" type="button" data-sort="views">Most viewed</button>
<button class="sort" type="button" data-sort="favs">Most favorited</button>
</div>
</div>
<div class="grid" id="grid"></div>
<p class="empty" id="empty" hidden>No projects in this channel yet — be the first to publish one.</p>
</section>
<!-- CONTESTS -->
<section class="contests" aria-labelledby="contestTitle">
<div class="wrap">
<div class="sec-head inv">
<h2 id="contestTitle">Contests running now</h2>
<span class="mono muted-inv">Entries close at 23:59 local time</span>
</div>
<div class="contest-row">
<article class="contest c1">
<span class="c-tag">Circuits</span>
<h3>Glow & Blink Challenge</h3>
<p class="prize">Grand prize <b>oscilloscope kit</b> + 3 runner-up soldering stations</p>
<p class="entries mono">312 entries</p>
<div class="count" data-deadline="6:04:20:00" aria-live="off"></div>
<button class="btn primary sm" type="button" data-toast="Entry started for Glow & Blink Challenge.">Enter</button>
</article>
<article class="contest c2">
<span class="c-tag">Workshop</span>
<h3>Scrap Wood Reuse</h3>
<p class="prize">Grand prize <b>track saw</b> + hardwood bundle</p>
<p class="entries mono">487 entries</p>
<div class="count" data-deadline="2:11:35:00"></div>
<button class="btn primary sm" type="button" data-toast="Entry started for Scrap Wood Reuse.">Enter</button>
</article>
<article class="contest c3">
<span class="c-tag">Cooking</span>
<h3>One-Pan Everything</h3>
<p class="prize">Grand prize <b>carbon steel set</b> + spice library</p>
<p class="entries mono">208 entries</p>
<div class="count" data-deadline="0:07:12:40"></div>
<button class="btn primary sm" type="button" data-toast="Entry started for One-Pan Everything.">Enter</button>
</article>
</div>
</div>
</section>
<!-- TRENDING -->
<section class="wrap trend" aria-labelledby="trendTitle">
<h2 id="trendTitle">Trending makers</h2>
<div class="trend-row" id="trendRow"></div>
</section>
<!-- NEWSLETTER -->
<section class="news" aria-labelledby="newsTitle">
<div class="wrap news-in">
<div>
<h2 id="newsTitle">Ten good builds, every Thursday</h2>
<p>Hand-picked projects, contest reminders, and one weird jig we could not stop thinking about.</p>
</div>
<form class="news-form" id="newsForm" novalidate>
<label class="sr" for="email">Email address</label>
<input id="email" name="email" type="email" placeholder="[email protected]" aria-describedby="emailErr" />
<button class="btn primary" type="submit">Subscribe</button>
<p class="err" id="emailErr" role="alert"></p>
</form>
</div>
</section>
</main>
<footer class="site-foot">
<div class="wrap foot-in">
<span class="brand-word sm">MAKE<em>LOG</em></span>
<nav aria-label="Footer"><a href="#">Channels</a><a href="#">Contests</a><a href="#">Community rules</a><a href="#">Safety</a></nav>
<span class="mono muted">Fictional demo · no real brand affiliation</span>
</div>
</footer>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Project Sharing Landing
MAKELOG is a fictional community where people post step-by-step builds. The page opens with a sticky header — wordmark, search field, Publish call to action — sitting above a channel bar for Circuits, Workshop, Craft, Cooking and Living, each marked with its own colour dot. Below that, a wide featured card carries a diagonal “Featured” ribbon, a gradient-overlaid illustration, the author chip and the usual step/view/favorite counters.
The feed itself is sixteen project cards in an auto-filling grid, some deliberately taller so the layout reads as masonry-ish rather than a rigid table. Each card has a channel-coloured top strip, a lift on hover that fades in a quick-view overlay with the build’s hook and step count, and a dashed footer with view and favorite counts in monospace. Cards are keyboard-operable buttons with visible focus.
Switching channels re-filters and re-staggers the grid, the sort pills reorder by views or
favorites, and typing in the search box (or pressing /) narrows by title, author or channel.
The dark contests band runs three independent countdown timers down to the second, the trending
makers row toggles follow state, and the newsletter band validates inline as you type and confirms
on submit. A single toast() helper acknowledges every action.
How this look is built
The information architecture is in the spirit of Instructables-style project communities — channel
navigation over a dense photo grid with contests running alongside — but the brand, copy and art are
original. Modern CSS does the heavy lifting: grid-template-columns: repeat(auto-fill, minmax(...))
gives a responsive feed with no breakpoint math, layered linear-gradient/radial-gradient/
repeating-conic-gradient on ::before and ::after synthesise every thumbnail so the page has
zero image requests, position: sticky keeps the header and channel bar in reach, and a
padding-bottom aspect box plus an opacity-transitioned overlay produce the hover quick view without
layout shift. The staggered entrance is a tiny per-card setTimeout adding an .in class, which
keeps the animation in CSS and disables cleanly under prefers-reduced-motion — far lighter than
the old approach of a JS masonry library measuring and absolutely positioning every tile on resize.
Illustrative UI only — always follow real safety guidance when working with tools, electronics, or repairs.