mirror of
https://github.com/Nexoniarz/Fry3DX.git
synced 2026-11-03 04:24:26 +00:00
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:
parent
62afbf2b1e
commit
87436019b3
4 changed files with 103 additions and 27 deletions
|
|
@ -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: [
|
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." },
|
{ 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." },
|
||||||
{ 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: "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." },
|
{ 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." },
|
||||||
{ 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: "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." },
|
{ 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." },
|
||||||
{ 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: "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." },
|
||||||
{ 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: "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." },
|
{ 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." },
|
||||||
{ 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: "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." },
|
{ 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." },
|
||||||
{ 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: "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." },
|
{ 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." },
|
||||||
{ 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: "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: [
|
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." },
|
{ 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: {
|
formats: {
|
||||||
open: [".fry"],
|
open: [".fry"],
|
||||||
save: [".fry (packed, with pictures)"],
|
save: [".fry (packed, with pictures)"],
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
<!-- The page is built from content.js by site.js: edit content.js to update it. -->
|
<!-- 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">
|
<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>
|
<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="#features">Features</a></li>
|
||||||
<li><a class="nv-nav__item" href="#look">Look</a></li>
|
<li><a class="nv-nav__item" href="#look">Look</a></li>
|
||||||
<li><a class="nv-nav__item" href="#scripting">Scripting</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>
|
<span class="nv-overline">What it does</span>
|
||||||
<h2 class="nv-heading">The useful parts of the big modellers, without the hundred menus.</h2>
|
<h2 class="nv-heading">The useful parts of the big modellers, without the hundred menus.</h2>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<section class="shell section" id="look">
|
<section class="shell section" id="look">
|
||||||
|
|
@ -95,8 +96,8 @@
|
||||||
<span class="nv-overline">How it looks</span>
|
<span class="nv-overline">How it looks</span>
|
||||||
<h2 class="nv-heading">Five tabs: what you're doing, not where it's hidden.</h2>
|
<h2 class="nv-heading">Five tabs: what you're doing, not where it's hidden.</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="nv-nav-tabs" role="tablist" aria-label="Screenshots" id="shot-tabs" data-nv-tabs></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="shots"></div>
|
<div id="shot-panels" class="tab-box shots"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="shell section" id="scripting" hidden>
|
<section class="shell section" id="scripting" hidden>
|
||||||
|
|
|
||||||
|
|
@ -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__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); }
|
.feature .nv-card__title { margin-block-end: var(--nv-space-2); }
|
||||||
|
|
||||||
/* --- Screenshots ----------------------------------------------------------- */
|
/* --- Tabs: the panel box they hand over in (Niveus Continuity) ---------------- */
|
||||||
.shots { margin-block-start: var(--nv-space-6); }
|
.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 { display: grid; gap: var(--nv-space-4); }
|
||||||
.shot[hidden] { display: none; }
|
.shot[hidden] { display: none; }
|
||||||
|
|
||||||
|
|
|
||||||
56
docs/site.js
56
docs/site.js
|
|
@ -47,14 +47,28 @@
|
||||||
$("why-title").textContent = F.why.title;
|
$("why-title").textContent = F.why.title;
|
||||||
for (const p of F.why.text) $("why-text").append(el("p", "nv-body", p));
|
for (const p of F.why.text) $("why-text").append(el("p", "nv-body", p));
|
||||||
|
|
||||||
// ── Features ──
|
// ── Features: a tab each group, the cards in its panel ──
|
||||||
for (const f of F.features) {
|
F.featureGroups.forEach((g, i) => {
|
||||||
const card = el("article", "nv-card feature");
|
const tab = el("button", "nv-nav__item", g.title);
|
||||||
const body = el("div", "nv-card__body");
|
tab.id = `feature-tab-${g.id}`;
|
||||||
body.append(icon(f.icon), el("h3", "nv-card__title", f.title), el("p", "nv-body nv-text-secondary", f.text));
|
tab.tabIndex = i ? -1 : 0;
|
||||||
card.append(body);
|
tab.setAttribute("role", "tab");
|
||||||
$("feature-grid").append(card);
|
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);
|
||||||
|
panel.append(card);
|
||||||
|
}
|
||||||
|
$("feature-panels").append(panel);
|
||||||
|
});
|
||||||
|
|
||||||
// ── Screenshots: a tab each, the picture in a window ──
|
// ── Screenshots: a tab each, the picture in a window ──
|
||||||
F.shots.forEach((s, i) => {
|
F.shots.forEach((s, i) => {
|
||||||
|
|
@ -169,6 +183,32 @@
|
||||||
addEventListener("scroll", onScroll, { passive: true });
|
addEventListener("scroll", onScroll, { passive: true });
|
||||||
onScroll();
|
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.
|
// A little Lua colouring for the scripting sample.
|
||||||
function highlightLua(src) {
|
function highlightLua(src) {
|
||||||
const esc = (t) => t.replace(/&/g, "&").replace(/</g, "<");
|
const esc = (t) => t.replace(/&/g, "&").replace(/</g, "<");
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue