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
16 changes: 12 additions & 4 deletions packages/react/src/scene/atlas/atlasPoly.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,18 @@ export const TextureAtlasPoly = memo(function TextureAtlasPoly({
const style: CSSProperties = {
transform: formatMatrix3d(entry.atlasMatrix),
["--polycss-atlas-size" as string]: `${atlasCanonicalSize}px`,
background,
backgroundImage: dynamic && page?.url ? `url(${page.url})` : undefined,
backgroundPosition: dynamic ? atlasPosition : undefined,
backgroundSize: dynamic ? atlasSize : undefined,
// Listing the `background` shorthand alongside the `background-*` longhands
// in one inline style object makes React warn on every update (mixing
// shorthand and non-shorthand for the same property). Branch so only the
// current mode's keys are assigned — baked gets `background`, dynamic gets
// the longhands.
...(dynamic
? {
backgroundImage: page?.url ? `url(${page.url})` : undefined,
backgroundPosition: atlasPosition,
backgroundSize: atlasSize,
}
: { background }),
...(dynamic
? {
["--pnx" as string]: entry.normal[0].toFixed(4),
Expand Down
47 changes: 43 additions & 4 deletions website/src/components/WordArtWorkbench/WordArtWorkbench.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ export function WordArtWorkbench() {
const [lightEl, setLightEl] = useState(() => qn("lel", 45));
const [activePreset, setActivePreset] = useState<string | null>(null);
const [exporting, setExporting] = useState(false);
// Mobile: only one floating panel is open at a time, toggled by the bottom tabs.
const [mobilePanel, setMobilePanel] = useState<"style" | "controls" | null>(null);

const handleCodePen = async () => {
const el = document.querySelector<HTMLElement>(".wa-stage .polycss-camera")
Expand All @@ -173,6 +175,13 @@ export function WordArtWorkbench() {
}
};

useEffect(() => {
if (!mobilePanel) return;
const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") setMobilePanel(null); };
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [mobilePanel]);

// Default bundled font + Google catalog. If the URL named a font, select it
// once the catalog is in.
useEffect(() => {
Expand Down Expand Up @@ -366,7 +375,11 @@ export function WordArtWorkbench() {
status={status}
/>

<aside className="wa-card wa-left" aria-label="Text and presets">
<aside
id="wa-style-panel"
className={`wa-card wa-left ${mobilePanel === "style" ? "is-mobile-open" : ""}`}
aria-label="Text and presets"
>
<div className="wa-card__body">
<label className="wa-field">
<span>Text</span>
Expand Down Expand Up @@ -398,11 +411,37 @@ export function WordArtWorkbench() {
</div>
</aside>

<GuiPanel values={guiValues} set={guiSet} />
<GuiPanel
id="wa-controls-panel"
className={mobilePanel === "controls" ? "is-mobile-open" : ""}
values={guiValues}
set={guiSet}
/>

<button type="button" className="wa-codepen" onClick={handleCodePen} disabled={exporting}>
{exporting ? "Exporting…" : "Open in CodePen"}
</button>

<nav className="wa-mobile-tabs" aria-label="WordArt panels">
<button
type="button"
className={`wa-mobile-tabs__button ${mobilePanel === "style" ? "is-active" : ""}`}
aria-controls="wa-style-panel"
aria-expanded={mobilePanel === "style"}
onClick={() => setMobilePanel((cur) => (cur === "style" ? null : "style"))}
>
Style
</button>
<button
type="button"
className={`wa-mobile-tabs__button ${mobilePanel === "controls" ? "is-active" : ""}`}
aria-controls="wa-controls-panel"
aria-expanded={mobilePanel === "controls"}
onClick={() => setMobilePanel((cur) => (cur === "controls" ? null : "controls"))}
>
Controls
</button>
</nav>
</div>
);
}
Expand Down Expand Up @@ -540,7 +579,7 @@ interface GuiValues {
* are identical, not a CSS approximation. lil-gui is imperative, so we mount it
* once and bridge its onChange → React, and React state → updateDisplay().
*/
function GuiPanel({ values, set }: { values: GuiValues; set: (k: keyof GuiValues, v: number | string | boolean) => void }) {
function GuiPanel({ id, className = "", values, set }: { id?: string; className?: string; values: GuiValues; set: (k: keyof GuiValues, v: number | string | boolean) => void }) {
const hostRef = useRef<HTMLDivElement>(null);
const cfgRef = useRef<GuiValues>({ ...values });
const ctrlRef = useRef<Record<string, ReturnType<GUI["add"]>>>({});
Expand Down Expand Up @@ -592,7 +631,7 @@ function GuiPanel({ values, set }: { values: GuiValues; set: (k: keyof GuiValues
ctrlRef.current.profileSegments?.[values.profile === "round" ? "show" : "hide"]();
});

return <div className="wa-gui" ref={hostRef} />;
return <div id={id} className={`wa-gui ${className}`} ref={hostRef} />;
}

interface LeftValues {
Expand Down
72 changes: 66 additions & 6 deletions website/src/components/WordArtWorkbench/wordart.css
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,6 @@
}
textarea.wa-input { resize: vertical; line-height: 1.35; height: auto; padding: 0.4rem 0.5rem; }

@media (max-width: 60rem) {
.wa-card { position: static; width: auto; max-height: none; margin: 12px; }
.wa-root { height: auto; overflow: visible; }
.wa-stage { position: relative; height: 56vh; inset: auto; }
}

/* Right panel = real lil-gui, themed with the /gallery's exact variables. */
.wa-gui {
position: absolute;
Expand Down Expand Up @@ -235,3 +229,69 @@ textarea.wa-input { resize: vertical; line-height: 1.35; height: auto; padding:
}
.wa-codepen:hover { border-color: #3a86ff; }
.wa-codepen:disabled { opacity: 0.6; cursor: progress; }

/* Bottom tab bar — desktop keeps both panels floating, so it's hidden there. */
.wa-mobile-tabs { display: none; }

/* Mobile: like /gallery — the floating panels collapse into bottom sheets that
the tab bar toggles one at a time, over a full-bleed stage. */
@media (max-width: 760px) {
.wa-root { --wa-tabs-h: 52px; }

.dn-stats-overlay { display: none !important; }

/* Only the two top-level floating panels collapse — not the left card's own
inline lil-gui (.wa-gui--inline), which stays inside the scrolling card. */
.wa-left,
.wa-gui:not(.wa-gui--inline) {
top: 8px;
right: 8px;
bottom: calc(8px + var(--wa-tabs-h) + 8px);
left: 8px;
width: auto;
max-width: none;
max-height: none;
z-index: 25;
display: none;
}
.wa-left.is-mobile-open { display: flex; }
.wa-gui:not(.wa-gui--inline).is-mobile-open { display: flex; flex-direction: column; }
.wa-left .wa-card__body { flex: 1; max-height: none; }

/* The right panel's lil-gui fills the sheet and scrolls inside it. */
.wa-gui:not(.wa-gui--inline) .lil-gui.root {
width: 100% !important;
height: 100% !important;
max-height: 100% !important;
}

.wa-codepen,
.wa-stage-foot { bottom: calc(8px + var(--wa-tabs-h) + 8px); }

.wa-mobile-tabs {
position: absolute;
right: 8px;
bottom: 8px;
left: 8px;
z-index: 30;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
}
.wa-mobile-tabs__button {
min-height: 44px;
border: 1px solid #2a3340;
border-radius: 8px;
background: rgba(17, 20, 26, 0.92);
color: #d8e2ec;
font: 700 13px/1 system-ui, sans-serif;
cursor: pointer;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
.wa-mobile-tabs__button.is-active {
border-color: #22d3ee;
background: #12313a;
color: #ecfeff;
}
}
Loading