Site: Niveus Continuity throughout — features in tabs that hand over left or right, screenshot tabs likewise, the bar's marker travelling to the section on screen, theme cross-fades; a Scripting section

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-10-20 17:10:53 +02:00
commit 87436019b3
4 changed files with 103 additions and 27 deletions

View file

@ -18,20 +18,29 @@ window.FRY = {
],
},
// The feature tabs, in order (each feature names its group).
featureGroups: [
{ id: "make", title: "Make" },
{ id: "paint", title: "Paint" },
{ id: "print", title: "Render & print" },
{ id: "work", title: "Work" },
],
features: [
{ icon: "cube", title: "Shapes that stay editable", text: "Cube, cylinder, sphere, torus, stairs, arch and more, plus starter models like a table, chair or barrel. Change their sizes any time in Properties." },
{ icon: "pen", title: "Draw a shape anywhere", text: "Click an outline on the ground or straight on the face of another object: it becomes a new part, gets added to it, or cuts into it." },
{ icon: "combine", title: "Add, subtract, overlap", text: "Combine objects: add them together, take one out of another, or keep only where they overlap. The result is always closed and ready to print." },
{ icon: "points", title: "Real shape editing", text: "Points, edges and faces. Extrude, inset, round edges, loop cut, knife, merge. Shift+click picks a whole loop, Shift+Ctrl+click a ring." },
{ icon: "hole", title: "Holes for screws and nuts", text: "Round, rectangle, triangle or hexagon. M2 to M8 screw and nut sizes ready, with a little room added so the print actually fits." },
{ icon: "copies", title: "Copies and thickness", text: "Copies in a row or round in a circle. Make a surface thick, or a solid hollow with walls exactly as thick as you need." },
{ icon: "brush", title: "Paint and stickers", text: "Paint on the model or on its flat picture. Paste any picture (Ctrl+V) and it goes on like a sticker that bends over edges and corners." },
{ icon: "layout", title: "Layout tab", text: "Move, turn and size the flat frames of the surface by hand. The paint follows, so the model looks the same." },
{ icon: "print", title: "Made for 3D printing", text: "Your printer's plate in the view, sizes in millimetres, checks for holes and flipped faces with fix buttons, overhangs shown red, weight and filament." },
{ icon: "sun", title: "See it rendered", text: "Solid view for modelling, a real renderer with sky, sun and soft shadows when you want to see it properly. Save a picture with F12." },
{ icon: "folder", title: "Folders and groups", text: "Layers with folders inside folders, drag and drop, show/hide and lock whole groups, and a button on every row that brings the camera to it." },
{ icon: "phone", title: "On your phone too", text: "Not a viewer: the whole modeller. Big buttons, one finger turns the view, two move and pinch to zoom, holding still is a right-click. Files through the phone's own picker." },
{ icon: "save", title: "Never lose work", text: "Its own packed .fry file, autosave in the background, recovery after a crash, and a Save / Don't save / Cancel when closing." },
{ group: "make", icon: "cube", title: "Shapes that stay editable", text: "Cube, cylinder, sphere, torus, stairs, arch and more, plus starter models like a table, chair or barrel. Change their sizes any time in Properties." },
{ group: "make", icon: "pen", title: "Draw a shape anywhere", text: "Click an outline on the ground or straight on the face of another object: it becomes a new part, gets added to it, or cuts into it." },
{ group: "make", icon: "combine", title: "Add, subtract, overlap", text: "Combine objects: add them together, take one out of another, or keep only where they overlap. The result is always closed and ready to print." },
{ group: "make", icon: "points", title: "Real shape editing", text: "Points, edges and faces. Extrude, inset, round edges, loop cut, knife, merge. Shift+click picks a whole loop, Shift+Ctrl+click a ring." },
{ group: "make", icon: "hole", title: "Holes for screws and nuts", text: "Round, rectangle, triangle or hexagon. M2 to M8 screw and nut sizes ready, with a little room added so the print actually fits." },
{ group: "make", icon: "copies", title: "Copies and thickness", text: "Copies in a row or round in a circle. Make a surface thick, or a solid hollow with walls exactly as thick as you need." },
{ group: "paint", icon: "brush", title: "Paint and stickers", text: "Paint on the model or on its flat picture. Paste any picture (Ctrl+V) and it goes on like a sticker that bends over edges and corners." },
{ group: "paint", icon: "layout", title: "Layout tab", text: "Move, turn and size the flat frames of the surface by hand. The paint follows, so the model looks the same." },
{ group: "print", icon: "print", title: "Made for 3D printing", text: "Your printer's plate in the view, sizes in millimetres, checks for holes and flipped faces with fix buttons, overhangs shown red, weight and filament." },
{ group: "print", icon: "sun", title: "See it rendered", text: "Solid view for modelling, a real renderer with sky, sun and soft shadows when you want to see it properly. Save a picture with F12." },
{ group: "work", icon: "folder", title: "Folders and groups", text: "Layers with folders inside folders, drag and drop, show/hide and lock whole groups, and a button on every row that brings the camera to it." },
{ group: "work", icon: "phone", title: "On your phone too", text: "Not a viewer: the whole modeller. Big buttons, one finger turns the view, two move and pinch to zoom, holding still is a right-click. Files through the phone's own picker." },
{ group: "work", icon: "code", title: "Scripts, if you want them", text: "Lua in a Script panel: build a town in a loop, turn a profile into a vase, move points by a formula. F5 runs it, Ctrl+Z takes it all back." },
{ group: "work", icon: "save", title: "Never lose work", text: "Its own packed .fry file, autosave in the background, recovery after a crash, and a Save / Don't save / Cancel when closing." },
],
shots: [
@ -43,6 +52,30 @@ window.FRY = {
{ img: "img/crash.webp", title: "Even the crash screen", text: "If something does get burnt, your work comes back from the last autosave." },
],
scripting: {
title: "When clicking gets repetitive, write it down.",
text: "Fry3DX has Lua built in, and a Script panel to write it in. Everything you can click, a script can do: add shapes and change their settings, combine them, move every point by a formula, set the sun. It's optional, small, and can't touch anything outside the scene.",
points: [
"F5 runs it; one Ctrl+Z takes back everything it did",
"If it stops with an error, nothing it did is kept",
"Examples to start from: stairs, a vase, a gear, a town, a box with a lid",
"The ? button lists every function, right next to your script",
],
file: "spiral_stairs.lua",
code: `-- Spiral stairs round a pillar: change the numbers, press F5 again.
local steps, rise, turn = 18, 0.18, 22
fry.add("pillar", {radius = 0.12, height = steps * rise + 0.3})
for i = 0, steps - 1 do
local step = fry.add("cube", {width = 0.9, depth = 0.32, height = 0.05,
color = "#b07a4a"})
local a = math.rad(i * turn)
step.turn = {0, 0, i * turn}
step.position = {math.cos(a) * 0.45, math.sin(a) * 0.45, (i + 1) * rise}
end
fry.frame()`,
},
formats: {
open: [".fry"],
save: [".fry (packed, with pictures)"],

View file

@ -28,7 +28,7 @@
<!-- The page is built from content.js by site.js: edit content.js to update it. -->
<nav class="nv-nav" id="top-nav" aria-label="Main">
<a class="nv-nav__brand" href="#top"><img class="brand-logo" src="img/logo.svg" alt=""> Fry3DX</a>
<ul class="nv-nav__list nav-links">
<ul class="nv-nav__list nav-links" id="nav-list" style="--nv-selection-name: nv-nav-selection">
<li><a class="nv-nav__item" href="#features">Features</a></li>
<li><a class="nv-nav__item" href="#look">Look</a></li>
<li><a class="nv-nav__item" href="#scripting">Scripting</a></li>
@ -87,7 +87,8 @@
<span class="nv-overline">What it does</span>
<h2 class="nv-heading">The useful parts of the big modellers, without the hundred menus.</h2>
</div>
<div class="features" id="feature-grid"></div>
<div class="nv-nav-tabs" role="tablist" aria-label="What it does" id="feature-tabs" data-nv-tabs style="--nv-selection-name: nv-feature-tabs"></div>
<div class="tab-box" id="feature-panels"></div>
</section>
<section class="shell section" id="look">
@ -95,8 +96,8 @@
<span class="nv-overline">How it looks</span>
<h2 class="nv-heading">Five tabs: what you're doing, not where it's hidden.</h2>
</div>
<div class="nv-nav-tabs" role="tablist" aria-label="Screenshots" id="shot-tabs" data-nv-tabs></div>
<div id="shot-panels" class="shots"></div>
<div class="nv-nav-tabs" role="tablist" aria-label="Screenshots" id="shot-tabs" data-nv-tabs style="--nv-selection-name: nv-shot-tabs"></div>
<div id="shot-panels" class="tab-box shots"></div>
</section>
<section class="shell section" id="scripting" hidden>

View file

@ -70,8 +70,10 @@ body { background: var(--nv-color-background); }
.feature__icon svg { inline-size: var(--nv-space-5); block-size: var(--nv-space-5); }
.feature .nv-card__title { margin-block-end: var(--nv-space-2); }
/* --- Screenshots ----------------------------------------------------------- */
.shots { margin-block-start: var(--nv-space-6); }
/* --- Tabs: the panel box they hand over in (Niveus Continuity) ---------------- */
.tab-box { margin-block-start: var(--nv-space-6); display: grid; }
.tab-box > [hidden] { display: none; }
.features[hidden] { display: none; }
.shot { display: grid; gap: var(--nv-space-4); }
.shot[hidden] { display: none; }

View file

@ -47,14 +47,28 @@
$("why-title").textContent = F.why.title;
for (const p of F.why.text) $("why-text").append(el("p", "nv-body", p));
// ── Features ──
for (const f of F.features) {
// ── Features: a tab each group, the cards in its panel ──
F.featureGroups.forEach((g, i) => {
const tab = el("button", "nv-nav__item", g.title);
tab.id = `feature-tab-${g.id}`;
tab.tabIndex = i ? -1 : 0;
tab.setAttribute("role", "tab");
tab.setAttribute("aria-controls", `features-${g.id}`);
tab.setAttribute("aria-selected", String(i === 0));
$("feature-tabs").append(tab);
const panel = el("div", "nv-tab-panel features");
Object.assign(panel, { id: `features-${g.id}`, hidden: i !== 0 });
panel.setAttribute("role", "tabpanel");
panel.setAttribute("aria-labelledby", tab.id);
for (const f of F.features.filter((f) => f.group === g.id)) {
const card = el("article", "nv-card feature");
const body = el("div", "nv-card__body");
body.append(icon(f.icon), el("h3", "nv-card__title", f.title), el("p", "nv-body nv-text-secondary", f.text));
card.append(body);
$("feature-grid").append(card);
panel.append(card);
}
$("feature-panels").append(panel);
});
// ── Screenshots: a tab each, the picture in a window ──
F.shots.forEach((s, i) => {
@ -169,6 +183,32 @@
addEventListener("scroll", onScroll, { passive: true });
onScroll();
// The bar's marker travels to the section on screen (Niveus Continuity: it moves there, it
// doesn't jump), forward or back by which way you went.
const links = [...document.querySelectorAll("#nav-list .nv-nav__item")];
const sections = links.map((a) => document.querySelector(a.getAttribute("href")));
let current = -1;
const mark = (i) => {
if (i === current) return;
const forward = i > current;
const update = () => links.forEach((a, j) => (j === i ? a.setAttribute("aria-current", "location") : a.removeAttribute("aria-current")));
current = i;
// (Not over another change that's still moving — a tab handing over: starting one skips it.)
const busy = document.documentElement.hasAttribute("data-nv-direction");
if (window.nv && !busy) nv.transition(update, { direction: forward ? "forward" : "backward", scope: $("nav-list") });
else update();
};
let queued = false;
const spy = () => {
queued = false;
const line = innerHeight * 0.35;
let at = -1;
sections.forEach((sec, i) => { if (sec && !sec.hidden && sec.getBoundingClientRect().top <= line) at = i; });
mark(at);
};
addEventListener("scroll", () => { if (!queued) queued = true, requestAnimationFrame(spy); }, { passive: true });
spy();
// A little Lua colouring for the scripting sample.
function highlightLua(src) {
const esc = (t) => t.replace(/&/g, "&amp;").replace(/</g, "&lt;");