Enhance UI with dark mode support and update README with TODO list. Added PCB components to BOM and improved styling for various components to support dark mode. Updated Tailwind configuration to enable dark mode.
Some checks failed
Build and Publish Docker Image / build-and-push (push) Has been cancelled

This commit is contained in:
MunchDev-oss
2026-01-04 19:19:55 -05:00
parent 4239125455
commit 87b9ff88ef
18 changed files with 590 additions and 311 deletions

View File

@@ -1,6 +1,7 @@
import { useState, useEffect } from 'react';
import MainPage from './components/MainPage';
import Wizard from './components/Wizard';
import ThemeToggle from './components/ThemeToggle';
import partsData from './data/index.js';
import { getSharedConfig } from './utils/shareService';
@@ -145,16 +146,24 @@ function App() {
};
if (!buildType) {
return <MainPage onSelectBuildType={handleSelectBuildType} />;
return (
<>
<ThemeToggle />
<MainPage onSelectBuildType={handleSelectBuildType} />
</>
);
}
return (
<Wizard
buildType={buildType}
initialConfig={config}
updateConfig={updateConfig}
onBackToMain={handleBackToMain}
/>
<>
<ThemeToggle />
<Wizard
buildType={buildType}
initialConfig={config}
updateConfig={updateConfig}
onBackToMain={handleBackToMain}
/>
</>
);
}

View File

@@ -129,6 +129,19 @@ export default function BOMSummary({ config }) {
});
}
// Include PCB mount parts if a PCB mount is selected
if (config.pcbMount) {
const pcbMountId = config.pcbMount.id;
const pcbMountComponent = partsData.components?.[pcbMountId];
if (pcbMountComponent?.printedParts) {
pcbMountComponent.printedParts.forEach((part) => {
if (part.required) {
parts.push({ ...part, category: 'PCB Mount' });
}
});
}
}
// Include toy mount parts if any toy mounts are selected
if (config.toyMountOptions && config.toyMountOptions.length > 0) {
if (partsData.components?.toyMounts?.printedParts) {
@@ -274,6 +287,7 @@ export default function BOMSummary({ config }) {
if (config.standFeet) selectedOptionIds.add(config.standFeet.id);
if (config.mount) selectedOptionIds.add(config.mount.id);
if (config.cover) selectedOptionIds.add(config.cover.id);
if (config.pcbMount) selectedOptionIds.add(config.pcbMount.id);
if (config.remoteKnob) selectedOptionIds.add(config.remoteKnob.id);
if (config.toyMountOptions && config.toyMountOptions.length > 0) {
config.toyMountOptions.forEach(opt => selectedOptionIds.add(opt.id));
@@ -540,6 +554,7 @@ export default function BOMSummary({ config }) {
if (config.standFeet) selectedOptionIds.add(config.standFeet.id);
if (config.mount) selectedOptionIds.add(config.mount.id);
if (config.cover) selectedOptionIds.add(config.cover.id);
if (config.pcbMount) selectedOptionIds.add(config.pcbMount.id);
if (config.remoteKnob) selectedOptionIds.add(config.remoteKnob.id);
if (config.toyMountOptions && config.toyMountOptions.length > 0) {
config.toyMountOptions.forEach(opt => selectedOptionIds.add(opt.id));
@@ -650,7 +665,7 @@ export default function BOMSummary({ config }) {
// Define main sections and their subcategories
const mainSections = {
'Actuator + Mount': ['Actuator Body', 'Mount', 'Cover'],
'Actuator + Mount': ['Actuator Body', 'Mount', 'Cover', 'PCB Mount'],
'Stand': ['Stand', 'Stand Hinges', 'Stand Feet', 'Stand Crossbar Supports'],
'Remote': ['Remote Body', 'Remote Knobs'],
};
@@ -669,13 +684,13 @@ export default function BOMSummary({ config }) {
return (
<div>
<h2 className="text-2xl font-bold mb-4">BOM Summary</h2>
<p className="text-gray-600 mb-6">
<h2 className="text-2xl font-bold mb-4 text-gray-900 dark:text-white">BOM Summary</h2>
<p className="text-gray-600 dark:text-gray-300 mb-6">
Review your configuration and bill of materials.
</p>
{/* Tab Navigation */}
<div className="border-b border-gray-200 mb-6">
<div className="border-b border-gray-200 dark:border-gray-700 mb-6">
<nav className="-mb-px flex space-x-8">
{tabs.map((tab) => (
<button
@@ -685,8 +700,8 @@ export default function BOMSummary({ config }) {
py-4 px-1 border-b-2 font-medium text-sm transition-colors
${
activeTab === tab.id
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
? 'border-blue-500 dark:border-blue-400 text-blue-600 dark:text-blue-400'
: 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-600'
}
`}
>
@@ -703,8 +718,8 @@ export default function BOMSummary({ config }) {
<div className="space-y-6">
{/* Hardware (Motor & Power Supply) */}
{(config.motor || config.powerSupply) && (
<div className="border-b border-gray-200 pb-4">
<h3 className="text-lg font-semibold mb-4">Hardware</h3>
<div className="border-b border-gray-200 dark:border-gray-700 pb-4">
<h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">Hardware</h3>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4">
{config.motor && (
<div className="flex flex-col items-center">
@@ -712,14 +727,14 @@ export default function BOMSummary({ config }) {
<img
src={config.motor.image}
alt={config.motor.name}
className="h-32 w-32 object-contain rounded-lg bg-gray-100 mb-2"
className="h-32 w-32 object-contain rounded-lg bg-gray-100 dark:bg-gray-700 mb-2"
onError={(e) => {
e.target.style.display = 'none';
}}
/>
)}
<span className="text-xs text-center text-gray-700 font-medium">{config.motor.name}</span>
<span className="text-xs text-center text-gray-600 mt-1">{formatPrice(config.motor.price)}</span>
<span className="text-xs text-center text-gray-700 dark:text-gray-300 font-medium">{config.motor.name}</span>
<span className="text-xs text-center text-gray-600 dark:text-gray-400 mt-1">{formatPrice(config.motor.price)}</span>
</div>
)}
{config.powerSupply && (
@@ -728,14 +743,14 @@ export default function BOMSummary({ config }) {
<img
src={config.powerSupply.image}
alt={config.powerSupply.name}
className="h-32 w-32 object-contain rounded-lg bg-gray-100 mb-2"
className="h-32 w-32 object-contain rounded-lg bg-gray-100 dark:bg-gray-700 mb-2"
onError={(e) => {
e.target.style.display = 'none';
}}
/>
)}
<span className="text-xs text-center text-gray-700 font-medium">{config.powerSupply.name}</span>
<span className="text-xs text-center text-gray-600 mt-1">{formatPrice(config.powerSupply.price)}</span>
<span className="text-xs text-center text-gray-700 dark:text-gray-300 font-medium">{config.powerSupply.name}</span>
<span className="text-xs text-center text-gray-600 dark:text-gray-400 mt-1">{formatPrice(config.powerSupply.price)}</span>
</div>
)}
</div>
@@ -744,20 +759,20 @@ export default function BOMSummary({ config }) {
{/* Filament Usage */}
{(filamentTotals.total > 0 || totalTime !== '0m') && (
<div className="border-b border-gray-200 pb-4">
<h3 className="text-lg font-semibold mb-2">Filament Usage</h3>
<div className="border-b border-gray-200 dark:border-gray-700 pb-4">
<h3 className="text-lg font-semibold mb-2 text-gray-900 dark:text-white">Filament Usage</h3>
<div className="space-y-2">
{filamentTotals.total > 0 && (
<div className="space-y-1">
<div className="flex justify-between items-center">
<span className="font-semibold text-gray-700">Total Filament:</span>
<span className="font-bold text-gray-900">{Math.round(filamentTotals.total)}g</span>
<span className="font-semibold text-gray-700 dark:text-gray-300">Total Filament:</span>
<span className="font-bold text-gray-900 dark:text-white">{Math.round(filamentTotals.total)}g</span>
</div>
{filamentTotals.primary > 0 && (
<div className="flex justify-between items-center text-sm text-gray-600 ml-4">
<div className="flex justify-between items-center text-sm text-gray-600 dark:text-gray-400 ml-4">
<div className="flex items-center gap-2">
<div
className="w-4 h-4 rounded-full border border-gray-300"
className="w-4 h-4 rounded-full border border-gray-300 dark:border-gray-600"
style={{ backgroundColor: getColorHex(config.primaryColor, 'primary') }}
/>
<span>Primary ({getColorName(config.primaryColor, 'primary')}):</span>
@@ -766,10 +781,10 @@ export default function BOMSummary({ config }) {
</div>
)}
{filamentTotals.secondary > 0 && (
<div className="flex justify-between items-center text-sm text-gray-600 ml-4">
<div className="flex justify-between items-center text-sm text-gray-600 dark:text-gray-400 ml-4">
<div className="flex items-center gap-2">
<div
className="w-4 h-4 rounded-full border border-gray-300"
className="w-4 h-4 rounded-full border border-gray-300 dark:border-gray-600"
style={{ backgroundColor: getColorHex(config.accentColor, 'accent') }}
/>
<span>Secondary ({getColorName(config.accentColor, 'accent')}):</span>
@@ -780,9 +795,9 @@ export default function BOMSummary({ config }) {
</div>
)}
{totalTime !== '0m' && (
<div className="flex justify-between items-center pt-2 border-t border-gray-100">
<span className="font-semibold text-gray-700">Total Printing Time:</span>
<span className="font-bold text-gray-900">{totalTime}</span>
<div className="flex justify-between items-center pt-2 border-t border-gray-100 dark:border-gray-800">
<span className="font-semibold text-gray-700 dark:text-gray-300">Total Printing Time:</span>
<span className="font-bold text-gray-900 dark:text-white">{totalTime}</span>
</div>
)}
</div>
@@ -790,11 +805,11 @@ export default function BOMSummary({ config }) {
)}
{/* Selected Options/Kit */}
{(config.mount || config.cover || config.standHinge || config.standFeet ||
{(config.mount || config.cover || config.pcbMount || config.standHinge || config.standFeet ||
(config.standCrossbarSupports && config.standCrossbarSupports.length > 0) ||
(config.remoteType || config.remote?.id)) && (
<div className="border-b border-gray-200 pb-4">
<h3 className="text-lg font-semibold mb-4">Selected Options</h3>
<div className="border-b border-gray-200 dark:border-gray-700 pb-4">
<h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">Selected Options</h3>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4">
{config.mount && (
<div className="flex flex-col items-center">
@@ -808,7 +823,7 @@ export default function BOMSummary({ config }) {
}}
/>
)}
<span className="text-xs text-center text-gray-700 font-medium">{config.mount.name}</span>
<span className="text-xs text-center text-gray-700 dark:text-gray-300 font-medium">{config.mount.name}</span>
</div>
)}
{config.cover && (
@@ -823,7 +838,22 @@ export default function BOMSummary({ config }) {
}}
/>
)}
<span className="text-xs text-center text-gray-700 font-medium">{config.cover.name}</span>
<span className="text-xs text-center text-gray-700 dark:text-gray-300 font-medium">{config.cover.name}</span>
</div>
)}
{config.pcbMount && (
<div className="flex flex-col items-center">
{config.pcbMount.image && (
<img
src={config.pcbMount.image}
alt={config.pcbMount.name}
className="h-32 w-32 object-contain rounded-lg bg-gray-100 mb-2"
onError={(e) => {
e.target.style.display = 'none';
}}
/>
)}
<span className="text-xs text-center text-gray-700 dark:text-gray-300 font-medium">{config.pcbMount.name}</span>
</div>
)}
{config.standHinge && (
@@ -838,7 +868,7 @@ export default function BOMSummary({ config }) {
}}
/>
)}
<span className="text-xs text-center text-gray-700 font-medium">{config.standHinge.name}</span>
<span className="text-xs text-center text-gray-700 dark:text-gray-300 font-medium">{config.standHinge.name}</span>
</div>
)}
{config.standFeet && (
@@ -853,7 +883,7 @@ export default function BOMSummary({ config }) {
}}
/>
)}
<span className="text-xs text-center text-gray-700 font-medium">{config.standFeet.name}</span>
<span className="text-xs text-center text-gray-700 dark:text-gray-300 font-medium">{config.standFeet.name}</span>
</div>
)}
{config.standCrossbarSupports && config.standCrossbarSupports.length > 0 && (
@@ -864,13 +894,13 @@ export default function BOMSummary({ config }) {
<img
src={support.image}
alt={support.name}
className="h-32 w-32 object-contain rounded-lg bg-gray-100 mb-2"
className="h-32 w-32 object-contain rounded-lg bg-gray-100 dark:bg-gray-700 mb-2"
onError={(e) => {
e.target.style.display = 'none';
}}
/>
)}
<span className="text-xs text-center text-gray-700 font-medium">{support.name}</span>
<span className="text-xs text-center text-gray-700 dark:text-gray-300 font-medium">{support.name}</span>
</div>
))}
</>
@@ -890,7 +920,7 @@ export default function BOMSummary({ config }) {
}}
/>
)}
<span className="text-xs text-center text-gray-700 font-medium">{remoteSystem.name}</span>
<span className="text-xs text-center text-gray-700 dark:text-gray-300 font-medium">{remoteSystem.name}</span>
</div>
) : null;
})()}
@@ -900,8 +930,8 @@ export default function BOMSummary({ config }) {
{/* Toy Mounts */}
{config.toyMountOptions && config.toyMountOptions.length > 0 && (
<div className="border-b border-gray-200 pb-4">
<h3 className="text-lg font-semibold mb-4">Toy Mounts</h3>
<div className="border-b border-gray-200 dark:border-gray-700 pb-4">
<h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">Toy Mounts</h3>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4">
{config.toyMountOptions.map((toyMount) => (
<div key={toyMount.id} className="flex flex-col items-center">
@@ -915,9 +945,9 @@ export default function BOMSummary({ config }) {
}}
/>
)}
<span className="text-xs text-center text-gray-700 font-medium">{toyMount.name}</span>
<span className="text-xs text-center text-gray-700 dark:text-gray-300 font-medium">{toyMount.name}</span>
{toyMount.description && (
<span className="text-xs text-center text-gray-500 mt-1">{toyMount.description}</span>
<span className="text-xs text-center text-gray-500 dark:text-gray-400 mt-1">{toyMount.description}</span>
)}
</div>
))}
@@ -926,10 +956,10 @@ export default function BOMSummary({ config }) {
)}
{/* Total */}
<div className="pt-4 border-t-2 border-gray-300">
<div className="pt-4 border-t-2 border-gray-300 dark:border-gray-700">
<div className="flex justify-between items-center">
<h3 className="text-xl font-bold">Total Hardware Cost</h3>
<p className="text-2xl font-bold text-blue-600">${total.toFixed(2)}</p>
<h3 className="text-xl font-bold text-gray-900 dark:text-white">Total Hardware Cost</h3>
<p className="text-2xl font-bold text-blue-600 dark:text-blue-400">${total.toFixed(2)}</p>
</div>
</div>
</div>
@@ -940,8 +970,8 @@ export default function BOMSummary({ config }) {
<div className="space-y-6">
{printedParts.length > 0 ? (
<>
<div className="border-b border-gray-200 pb-4">
<h3 className="text-lg font-semibold mb-4">Required Printed Parts</h3>
<div className="border-b border-gray-200 dark:border-gray-700 pb-4">
<h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">Required Printed Parts</h3>
</div>
<div className="space-y-6">
{Object.entries(mainSections).map(([mainSectionName, subcategories]) => {
@@ -950,8 +980,8 @@ export default function BOMSummary({ config }) {
}
return (
<div key={mainSectionName} className="border-l-2 border-blue-200 pl-4">
<h4 className="text-lg font-semibold text-gray-800 mb-3">{mainSectionName}</h4>
<div key={mainSectionName} className="border-l-2 border-blue-200 dark:border-blue-700 pl-4">
<h4 className="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-3">{mainSectionName}</h4>
<div className="space-y-4 ml-2">
{subcategories.map((category) => {
const parts = partsByCategory[category];
@@ -962,26 +992,26 @@ export default function BOMSummary({ config }) {
return (
<div key={category}>
<div className="flex items-center gap-2 mb-3">
<h5 className="text-md font-medium text-gray-700">{category}</h5>
<h5 className="text-md font-medium text-gray-700 dark:text-gray-300">{category}</h5>
{parts.some(p => p.replacesActuatorMiddle) && (
<span className="text-xs text-blue-600 bg-blue-50 px-2 py-1 rounded">
<span className="text-xs text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/30 px-2 py-1 rounded">
Replaces standard ossm-actuator-body-middle
</span>
)}
</div>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 border border-gray-200 rounded-lg">
<thead className="bg-gray-50">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700 border border-gray-200 dark:border-gray-700 rounded-lg">
<thead className="bg-gray-50 dark:bg-gray-800">
<tr>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-700 uppercase tracking-wider">Part Name</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-700 uppercase tracking-wider">Color</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-700 uppercase tracking-wider">Description</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-700 uppercase tracking-wider">File Path</th>
<th className="px-4 py-3 text-right text-xs font-medium text-gray-700 uppercase tracking-wider">Quantity</th>
<th className="px-4 py-3 text-right text-xs font-medium text-gray-700 uppercase tracking-wider">Filament</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">Part Name</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">Color</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">Description</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">File Path</th>
<th className="px-4 py-3 text-right text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">Quantity</th>
<th className="px-4 py-3 text-right text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">Filament</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
{parts.map((part) => {
const partColour = part.colour || 'primary';
const colorHex = getColorHex(
@@ -994,51 +1024,51 @@ export default function BOMSummary({ config }) {
);
return (
<tr key={part.id} className="hover:bg-gray-50">
<tr key={part.id} className="hover:bg-gray-50 dark:hover:bg-gray-700/50">
<td className="px-4 py-3 whitespace-nowrap">
<p className="text-sm font-medium text-gray-900">{part.name}</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">{part.name}</p>
</td>
<td className="px-4 py-3 whitespace-nowrap">
<div className="flex items-center gap-2">
<div
className="w-4 h-4 rounded-full border border-gray-300"
className="w-4 h-4 rounded-full border border-gray-300 dark:border-gray-600"
style={{ backgroundColor: colorHex }}
title={`${partColour === 'primary' ? 'Primary' : 'Secondary'} color: ${colorName}`}
/>
<span className="text-xs text-gray-600 capitalize">{partColour}</span>
<span className="text-xs text-gray-600 dark:text-gray-400 capitalize">{partColour}</span>
</div>
</td>
<td className="px-4 py-3">
<p className="text-sm text-gray-600">{part.description || '-'}</p>
<p className="text-sm text-gray-600 dark:text-gray-300">{part.description || '-'}</p>
</td>
<td className="px-4 py-3">
{part.isHardwareOnly ? (
<span className="text-xs text-blue-600 italic">Hardware only</span>
<span className="text-xs text-blue-600 dark:text-blue-400 italic">Hardware only</span>
) : part.filePath ? (
<p className="text-xs text-gray-500 font-mono">{part.filePath}</p>
<p className="text-xs text-gray-500 dark:text-gray-400 font-mono">{part.filePath}</p>
) : (
<span className="text-gray-400">-</span>
<span className="text-gray-400 dark:text-gray-500">-</span>
)}
</td>
<td className="px-4 py-3 whitespace-nowrap text-right">
<p className="text-sm font-medium text-gray-700">{part.quantity || 1}</p>
<p className="text-sm font-medium text-gray-700 dark:text-gray-300">{part.quantity || 1}</p>
</td>
<td className="px-4 py-3 whitespace-nowrap text-right">
{part.isHardwareOnly ? (
<span className="text-xs text-blue-600">-</span>
<span className="text-xs text-blue-600 dark:text-blue-400">-</span>
) : part.filamentEstimate !== undefined && part.filamentEstimate > 0 ? (
<p className="text-sm font-medium text-gray-700">
<p className="text-sm font-medium text-gray-700 dark:text-gray-300">
{typeof part.filamentEstimate === 'number'
? (part.filamentEstimate * (part.quantity || 1)).toFixed(1)
: part.filamentEstimate}g
{(part.quantity || 1) > 1 && (
<span className="text-xs text-gray-500 ml-1">
<span className="text-xs text-gray-500 dark:text-gray-400 ml-1">
({(typeof part.filamentEstimate === 'number' ? part.filamentEstimate : parseFloat(part.filamentEstimate.replace(/[~g]/g, '').trim()) || 0).toFixed(1)}g × {part.quantity})
</span>
)}
</p>
) : (
<span className="text-gray-400">-</span>
<span className="text-gray-400 dark:text-gray-500">-</span>
)}
</td>
</tr>
@@ -1065,9 +1095,9 @@ export default function BOMSummary({ config }) {
return (
<div key={category}>
<div className="flex items-center gap-2 mb-3">
<h4 className="text-md font-medium text-gray-700">{category}</h4>
<h4 className="text-md font-medium text-gray-700 dark:text-gray-300">{category}</h4>
{parts.some(p => p.replacesActuatorMiddle) && (
<span className="text-xs text-blue-600 bg-blue-50 px-2 py-1 rounded">
<span className="text-xs text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/30 px-2 py-1 rounded">
Replaces standard ossm-actuator-body-middle
</span>
)}
@@ -1120,7 +1150,7 @@ export default function BOMSummary({ config }) {
) : part.filePath ? (
<p className="text-xs text-gray-500 font-mono">{part.filePath}</p>
) : (
<span className="text-gray-400">-</span>
<span className="text-gray-400 dark:text-gray-500">-</span>
)}
</td>
<td className="px-4 py-3 whitespace-nowrap text-right">
@@ -1133,13 +1163,13 @@ export default function BOMSummary({ config }) {
? (part.filamentEstimate * (part.quantity || 1)).toFixed(1)
: part.filamentEstimate}g
{(part.quantity || 1) > 1 && (
<span className="text-xs text-gray-500 ml-1">
<span className="text-xs text-gray-500 dark:text-gray-400 ml-1">
({(typeof part.filamentEstimate === 'number' ? part.filamentEstimate : parseFloat(part.filamentEstimate.replace(/[~g]/g, '').trim()) || 0).toFixed(1)}g × {part.quantity})
</span>
)}
</p>
) : (
<span className="text-gray-400">-</span>
<span className="text-gray-400 dark:text-gray-500">-</span>
)}
</td>
</tr>
@@ -1152,21 +1182,21 @@ export default function BOMSummary({ config }) {
);
})}
{(filamentTotals.total > 0 || totalTime !== '0m') && (
<div className="mt-4 pt-4 border-t border-gray-200 space-y-2">
<div className="mt-4 pt-4 border-t border-gray-200 dark:border-gray-700 space-y-2">
{filamentTotals.total > 0 && (
<div className="space-y-1">
<div className="flex justify-between items-center">
<span className="font-semibold text-gray-700">Total Filament Estimate:</span>
<span className="font-bold text-gray-900">{Math.round(filamentTotals.total)}g</span>
<span className="font-semibold text-gray-700 dark:text-gray-300">Total Filament Estimate:</span>
<span className="font-bold text-gray-900 dark:text-white">{Math.round(filamentTotals.total)}g</span>
</div>
{filamentTotals.primary > 0 && (
<div className="flex justify-between items-center text-sm text-gray-600 ml-4">
<div className="flex justify-between items-center text-sm text-gray-600 dark:text-gray-400 ml-4">
<span>Primary ({getColorName(config.primaryColor, 'primary')}):</span>
<span>{Math.round(filamentTotals.primary)}g</span>
</div>
)}
{filamentTotals.secondary > 0 && (
<div className="flex justify-between items-center text-sm text-gray-600 ml-4">
<div className="flex justify-between items-center text-sm text-gray-600 dark:text-gray-400 ml-4">
<span>Secondary ({getColorName(config.accentColor, 'accent')}):</span>
<span>{Math.round(filamentTotals.secondary)}g</span>
</div>
@@ -1174,9 +1204,9 @@ export default function BOMSummary({ config }) {
</div>
)}
{totalTime !== '0m' && (
<div className="flex justify-between items-center pt-2 border-t border-gray-100">
<span className="font-semibold text-gray-700">Total Printing Time:</span>
<span className="font-bold text-gray-900">{totalTime}</span>
<div className="flex justify-between items-center pt-2 border-t border-gray-100 dark:border-gray-800">
<span className="font-semibold text-gray-700 dark:text-gray-300">Total Printing Time:</span>
<span className="font-bold text-gray-900 dark:text-white">{totalTime}</span>
</div>
)}
</div>
@@ -1184,7 +1214,7 @@ export default function BOMSummary({ config }) {
</div>
</>
) : (
<div className="text-center py-12 text-gray-500">
<div className="text-center py-12 text-gray-500 dark:text-gray-400">
<p>No printed parts required for this configuration.</p>
</div>
)}
@@ -1196,16 +1226,16 @@ export default function BOMSummary({ config }) {
<div className="space-y-6">
{hardwareParts.length > 0 ? (
<>
<div className="border-b border-gray-200 pb-4">
<div className="border-b border-gray-200 dark:border-gray-700 pb-4">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold">Required Hardware Parts</h3>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Required Hardware Parts</h3>
<div className="flex gap-2">
<button
onClick={() => setHardwareViewMode('unified')}
className={`px-3 py-1.5 text-sm font-medium rounded-lg transition-colors ${
hardwareViewMode === 'unified'
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
? 'bg-blue-600 dark:bg-blue-500 text-white'
: 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600'
}`}
>
Unified View
@@ -1214,8 +1244,8 @@ export default function BOMSummary({ config }) {
onClick={() => setHardwareViewMode('expanded')}
className={`px-3 py-1.5 text-sm font-medium rounded-lg transition-colors ${
hardwareViewMode === 'expanded'
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
? 'bg-blue-600 dark:bg-blue-500 text-white'
: 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600'
}`}
>
Expanded View
@@ -1237,34 +1267,34 @@ export default function BOMSummary({ config }) {
return indexA - indexB;
}).map(([type, parts]) => (
<div key={type}>
<h4 className="text-md font-medium text-gray-700 mb-3">{type}</h4>
<h4 className="text-md font-medium text-gray-700 dark:text-gray-300 mb-3">{type}</h4>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 border border-gray-200 rounded-lg">
<thead className="bg-gray-50">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700 border border-gray-200 dark:border-gray-700 rounded-lg">
<thead className="bg-gray-50 dark:bg-gray-800">
<tr>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-700 uppercase tracking-wider">Part Name</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-700 uppercase tracking-wider">Description</th>
<th className="px-4 py-3 text-right text-xs font-medium text-gray-700 uppercase tracking-wider">Quantity</th>
<th className="px-4 py-3 text-right text-xs font-medium text-gray-700 uppercase tracking-wider">Price</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">Part Name</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">Description</th>
<th className="px-4 py-3 text-right text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">Quantity</th>
<th className="px-4 py-3 text-right text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">Price</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
{parts.map((part) => (
<tr key={part.id} className="hover:bg-gray-50">
<tr key={part.id} className="hover:bg-gray-50 dark:hover:bg-gray-700/50">
<td className="px-4 py-3 whitespace-nowrap">
<p className="text-sm font-medium text-gray-900">{part.name}</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">{part.name}</p>
</td>
<td className="px-4 py-3">
<p className="text-sm text-gray-600">{part.description || '-'}</p>
<p className="text-sm text-gray-600 dark:text-gray-300">{part.description || '-'}</p>
</td>
<td className="px-4 py-3 whitespace-nowrap text-right">
<p className="text-sm font-medium text-gray-700">{part.quantity || 1}</p>
<p className="text-sm font-medium text-gray-700 dark:text-gray-300">{part.quantity || 1}</p>
</td>
<td className="px-4 py-3 whitespace-nowrap text-right">
{part.price && part.price > 0 ? (
<p className="text-sm font-medium text-gray-700">{formatPrice(part.price)}</p>
<p className="text-sm font-medium text-gray-700 dark:text-gray-300">{formatPrice(part.price)}</p>
) : (
<span className="text-gray-400">-</span>
<span className="text-gray-400 dark:text-gray-500">-</span>
)}
</td>
</tr>
@@ -1278,40 +1308,40 @@ export default function BOMSummary({ config }) {
// Expanded view: Group by component BOMs (shows hardware breakdown by component)
expandedHardwareByComponent.map(({ component, parts }) => (
<div key={component}>
<h4 className="text-md font-medium text-gray-700 mb-3">{component}</h4>
<h4 className="text-md font-medium text-gray-700 dark:text-gray-300 mb-3">{component}</h4>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200 border border-gray-200 rounded-lg">
<thead className="bg-gray-50">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700 border border-gray-200 dark:border-gray-700 rounded-lg">
<thead className="bg-gray-50 dark:bg-gray-800">
<tr>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-700 uppercase tracking-wider">Part Name</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-700 uppercase tracking-wider">Description</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-700 uppercase tracking-wider">Type</th>
<th className="px-4 py-3 text-right text-xs font-medium text-gray-700 uppercase tracking-wider">Quantity</th>
<th className="px-4 py-3 text-right text-xs font-medium text-gray-700 uppercase tracking-wider">Price</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">Part Name</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">Description</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">Type</th>
<th className="px-4 py-3 text-right text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">Quantity</th>
<th className="px-4 py-3 text-right text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">Price</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
{parts.map((part) => (
<tr key={part.id} className="hover:bg-gray-50">
<tr key={part.id} className="hover:bg-gray-50 dark:hover:bg-gray-700/50">
<td className="px-4 py-3 whitespace-nowrap">
<p className="text-sm font-medium text-gray-900">{part.name}</p>
<p className="text-sm font-medium text-gray-900 dark:text-white">{part.name}</p>
</td>
<td className="px-4 py-3">
<p className="text-sm text-gray-600">{part.description || '-'}</p>
<p className="text-sm text-gray-600 dark:text-gray-300">{part.description || '-'}</p>
</td>
<td className="px-4 py-3 whitespace-nowrap">
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 dark:bg-blue-900/30 text-blue-800 dark:text-blue-300">
{part.hardwareType || 'Other Hardware'}
</span>
</td>
<td className="px-4 py-3 whitespace-nowrap text-right">
<p className="text-sm font-medium text-gray-700">{part.quantity || 1}</p>
<p className="text-sm font-medium text-gray-700 dark:text-gray-300">{part.quantity || 1}</p>
</td>
<td className="px-4 py-3 whitespace-nowrap text-right">
{part.price && part.price > 0 ? (
<p className="text-sm font-medium text-gray-700">{formatPrice(part.price)}</p>
<p className="text-sm font-medium text-gray-700 dark:text-gray-300">{formatPrice(part.price)}</p>
) : (
<span className="text-gray-400">-</span>
<span className="text-gray-400 dark:text-gray-500">-</span>
)}
</td>
</tr>
@@ -1325,7 +1355,7 @@ export default function BOMSummary({ config }) {
</div>
</>
) : (
<div className="text-center py-12 text-gray-500">
<div className="text-center py-12 text-gray-500 dark:text-gray-400">
<p>No hardware parts required for this configuration.</p>
</div>
)}
@@ -1352,7 +1382,7 @@ export default function BOMSummary({ config }) {
alert('Error creating share link. Please try again.');
}
}}
className="w-full px-6 py-3 bg-purple-600 text-white rounded-lg font-medium hover:bg-purple-700 transition-colors"
className="w-full px-6 py-3 bg-purple-600 dark:bg-purple-500 text-white rounded-lg font-medium hover:bg-purple-700 dark:hover:bg-purple-600 transition-colors"
>
Share Link (7 days)
</button>
@@ -1502,7 +1532,7 @@ export default function BOMSummary({ config }) {
}
}}
disabled={isExportingZip}
className="w-full px-6 py-3 bg-green-600 text-white rounded-lg font-medium hover:bg-green-700 transition-colors disabled:bg-gray-400 disabled:cursor-not-allowed"
className="w-full px-6 py-3 bg-green-600 dark:bg-green-500 text-white rounded-lg font-medium hover:bg-green-700 dark:hover:bg-green-600 transition-colors disabled:bg-gray-400 dark:disabled:bg-gray-600 disabled:cursor-not-allowed"
>
{isExportingZip ? (
<div className="flex flex-col items-center">
@@ -1512,14 +1542,14 @@ export default function BOMSummary({ config }) {
<div className="flex justify-between text-xs mb-1">
<span>{zipProgress.current}%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all duration-300"
className="bg-blue-600 dark:bg-blue-500 h-2 rounded-full transition-all duration-300"
style={{ width: `${zipProgress.current}%` }}
/>
</div>
{zipProgress.currentFile && (
<p className="text-xs text-gray-600 mt-1 truncate">{zipProgress.currentFile}</p>
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1 truncate">{zipProgress.currentFile}</p>
)}
</div>
)}
@@ -1531,7 +1561,7 @@ export default function BOMSummary({ config }) {
</div>
{/* Legacy export buttons (hidden but kept for backward compatibility) */}
<div className="text-xs text-gray-500 text-center">
<div className="text-xs text-gray-500 dark:text-gray-400 text-center">
Export includes: Overview (Markdown), BOM (Excel), Print List (Excel), and Print Files (organized by component/color)
</div>
</div>

View File

@@ -6,21 +6,21 @@ export default function MainPage({ onSelectBuildType }) {
};
return (
<div className="min-h-screen bg-gray-50 py-8">
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 py-8">
<div className="max-w-4xl mx-auto px-4">
{/* Header */}
<div className="mb-12 text-center">
<h1 className="text-4xl font-bold text-gray-900 mb-2">
<h1 className="text-4xl font-bold text-gray-900 dark:text-white mb-2">
OSSM Configurator
</h1>
<p className="text-gray-600">
<p className="text-gray-600 dark:text-gray-300">
Configure your Open Source Sex Machine
</p>
</div>
{/* Build Type Selection */}
<div className="bg-white rounded-lg shadow-lg p-8">
<h2 className="text-2xl font-bold text-gray-900 mb-6 text-center">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-8">
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-6 text-center">
Select Your Build Type
</h2>
@@ -28,11 +28,11 @@ export default function MainPage({ onSelectBuildType }) {
{/* New Build - RAD Kit */}
<button
onClick={() => handleSelect('rad-kit')}
className="flex flex-col items-center p-6 border-2 border-gray-200 rounded-lg hover:border-blue-500 hover:bg-blue-50 transition-all duration-200 group"
className="flex flex-col items-center p-6 border-2 border-gray-200 dark:border-gray-700 rounded-lg hover:border-blue-500 dark:hover:border-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-all duration-200 group"
>
<div className="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mb-4 group-hover:bg-blue-200 transition-colors">
<div className="w-16 h-16 bg-blue-100 dark:bg-blue-900/30 rounded-full flex items-center justify-center mb-4 group-hover:bg-blue-200 dark:group-hover:bg-blue-900/50 transition-colors">
<svg
className="w-8 h-8 text-blue-600"
className="w-8 h-8 text-blue-600 dark:text-blue-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
@@ -45,13 +45,13 @@ export default function MainPage({ onSelectBuildType }) {
/>
</svg>
</div>
<h3 className="text-xl font-semibold text-gray-900 mb-2">
<h3 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">
New Build
</h3>
<p className="text-sm font-medium text-blue-600 mb-3">
<p className="text-sm font-medium text-blue-600 dark:text-blue-400 mb-3">
Kit from RAD
</p>
<p className="text-sm text-gray-600 text-center">
<p className="text-sm text-gray-600 dark:text-gray-300 text-center">
Pre-configured kit with all required parts. Jump straight to the summary.
</p>
</button>
@@ -59,11 +59,11 @@ export default function MainPage({ onSelectBuildType }) {
{/* New Build - Self Source */}
<button
onClick={() => handleSelect('self-source')}
className="flex flex-col items-center p-6 border-2 border-gray-200 rounded-lg hover:border-green-500 hover:bg-green-50 transition-all duration-200 group"
className="flex flex-col items-center p-6 border-2 border-gray-200 dark:border-gray-700 rounded-lg hover:border-green-500 dark:hover:border-green-400 hover:bg-green-50 dark:hover:bg-green-900/20 transition-all duration-200 group"
>
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mb-4 group-hover:bg-green-200 transition-colors">
<div className="w-16 h-16 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center mb-4 group-hover:bg-green-200 dark:group-hover:bg-green-900/50 transition-colors">
<svg
className="w-8 h-8 text-green-600"
className="w-8 h-8 text-green-600 dark:text-green-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
@@ -76,13 +76,13 @@ export default function MainPage({ onSelectBuildType }) {
/>
</svg>
</div>
<h3 className="text-xl font-semibold text-gray-900 mb-2">
<h3 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">
New Build
</h3>
<p className="text-sm font-medium text-green-600 mb-3">
<p className="text-sm font-medium text-green-600 dark:text-green-400 mb-3">
Self Source
</p>
<p className="text-sm text-gray-600 text-center">
<p className="text-sm text-gray-600 dark:text-gray-300 text-center">
Go through the full wizard to select and customize all components.
</p>
</button>
@@ -90,11 +90,11 @@ export default function MainPage({ onSelectBuildType }) {
{/* Upgrade */}
<button
onClick={() => handleSelect('upgrade')}
className="flex flex-col items-center p-6 border-2 border-gray-200 rounded-lg hover:border-purple-500 hover:bg-purple-50 transition-all duration-200 group"
className="flex flex-col items-center p-6 border-2 border-gray-200 dark:border-gray-700 rounded-lg hover:border-purple-500 dark:hover:border-purple-400 hover:bg-purple-50 dark:hover:bg-purple-900/20 transition-all duration-200 group"
>
<div className="w-16 h-16 bg-purple-100 rounded-full flex items-center justify-center mb-4 group-hover:bg-purple-200 transition-colors">
<div className="w-16 h-16 bg-purple-100 dark:bg-purple-900/30 rounded-full flex items-center justify-center mb-4 group-hover:bg-purple-200 dark:group-hover:bg-purple-900/50 transition-colors">
<svg
className="w-8 h-8 text-purple-600"
className="w-8 h-8 text-purple-600 dark:text-purple-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
@@ -107,13 +107,13 @@ export default function MainPage({ onSelectBuildType }) {
/>
</svg>
</div>
<h3 className="text-xl font-semibold text-gray-900 mb-2">
<h3 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">
Upgrade / Mod
</h3>
<p className="text-sm font-medium text-purple-600 mb-3">
<p className="text-sm font-medium text-purple-600 dark:text-purple-400 mb-3">
Add Modifications
</p>
<p className="text-sm text-gray-600 text-center">
<p className="text-sm text-gray-600 dark:text-gray-300 text-center">
Browse and select upgrade components and modifications for your existing build.
</p>
</button>

View File

@@ -0,0 +1,45 @@
import { useTheme } from '../contexts/ThemeContext';
export default function ThemeToggle() {
const { theme, toggleTheme } = useTheme();
return (
<button
onClick={toggleTheme}
className="fixed top-4 right-4 z-50 p-3 rounded-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 shadow-lg hover:shadow-xl transition-all duration-200 hover:scale-110"
aria-label="Toggle theme"
>
{theme === 'dark' ? (
// Sun icon for light mode
<svg
className="w-6 h-6 text-yellow-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
/>
</svg>
) : (
// Moon icon for dark mode
<svg
className="w-6 h-6 text-gray-700"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"
/>
</svg>
)}
</button>
);
}

View File

@@ -158,14 +158,14 @@ export default function Wizard({ buildType = 'self-source', initialConfig, updat
}, [buildType, currentStep]);
return (
<div className="min-h-screen bg-gray-50 py-8">
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 py-8">
<div className="max-w-4xl mx-auto px-4">
{/* Back Button */}
{onBackToMain && (
<div className="mb-4">
<button
onClick={onBackToMain}
className="text-blue-600 hover:text-blue-800 font-medium flex items-center gap-2"
className="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 font-medium flex items-center gap-2"
>
<svg
className="w-5 h-5"
@@ -187,17 +187,17 @@ export default function Wizard({ buildType = 'self-source', initialConfig, updat
{/* Header */}
<div className="mb-8 text-center">
<h1 className="text-4xl font-bold text-gray-900 mb-2">
<h1 className="text-4xl font-bold text-gray-900 dark:text-white mb-2">
OSSM Configurator
</h1>
<p className="text-gray-600">
<p className="text-gray-600 dark:text-gray-300">
{buildType === 'upgrade'
? 'Select upgrade components and modifications'
: 'Configure your Open Source Sex Machine'}
</p>
{buildType === 'upgrade' && (
<div className="mt-4 bg-purple-50 border border-purple-200 rounded-lg p-3 inline-block">
<p className="text-purple-800 text-sm font-medium">
<div className="mt-4 bg-purple-50 dark:bg-purple-900/30 border border-purple-200 dark:border-purple-700 rounded-lg p-3 inline-block">
<p className="text-purple-800 dark:text-purple-300 text-sm font-medium">
Upgrade Mode: Only modification and upgrade components are shown
</p>
</div>
@@ -214,10 +214,10 @@ export default function Wizard({ buildType = 'self-source', initialConfig, updat
onClick={() => goToStep(index)}
className={`w-10 h-10 rounded-full flex items-center justify-center font-semibold flex-shrink-0 z-10 ${
index === currentStep
? 'bg-blue-600 text-white'
? 'bg-blue-600 dark:bg-blue-500 text-white'
: index < currentStep
? 'bg-green-500 text-white'
: 'bg-gray-200 text-gray-500'
? 'bg-green-500 dark:bg-green-600 text-white'
: 'bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400'
} ${
index <= currentStep
? 'cursor-pointer hover:opacity-80'
@@ -231,7 +231,7 @@ export default function Wizard({ buildType = 'self-source', initialConfig, updat
{index < filteredSteps.length - 1 && (
<div
className={`absolute top-5 left-1/2 h-1 ${
index < currentStep ? 'bg-green-500' : 'bg-gray-200'
index < currentStep ? 'bg-green-500 dark:bg-green-600' : 'bg-gray-200 dark:bg-gray-700'
}`}
style={{
width: 'calc(100% - 40px)',
@@ -244,8 +244,8 @@ export default function Wizard({ buildType = 'self-source', initialConfig, updat
onClick={() => goToStep(index)}
className={`mt-2 text-sm font-medium text-center ${
index <= currentStep
? 'text-blue-600 cursor-pointer hover:text-blue-800'
: 'text-gray-400 cursor-not-allowed'
? 'text-blue-600 dark:text-blue-400 cursor-pointer hover:text-blue-800 dark:hover:text-blue-300'
: 'text-gray-400 dark:text-gray-500 cursor-not-allowed'
}`}
disabled={!canNavigateToStep(index)}
>
@@ -257,7 +257,7 @@ export default function Wizard({ buildType = 'self-source', initialConfig, updat
</div>
{/* Step Content */}
<div className="bg-white rounded-lg shadow-lg p-6 md:p-8 mb-6">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 md:p-8 mb-6">
<CurrentStepComponent
config={config}
updateConfig={updateConfig}
@@ -275,8 +275,8 @@ export default function Wizard({ buildType = 'self-source', initialConfig, updat
disabled={currentStep === 0}
className={`px-6 py-2 rounded-lg font-medium ${
currentStep === 0
? 'bg-gray-200 text-gray-400 cursor-not-allowed'
: 'bg-gray-600 text-white hover:bg-gray-700'
? 'bg-gray-200 dark:bg-gray-700 text-gray-400 dark:text-gray-500 cursor-not-allowed'
: 'bg-gray-600 dark:bg-gray-700 text-white hover:bg-gray-700 dark:hover:bg-gray-600'
}`}
>
Previous
@@ -287,8 +287,8 @@ export default function Wizard({ buildType = 'self-source', initialConfig, updat
disabled={!canProceedToNextStep()}
className={`px-6 py-2 rounded-lg font-medium ${
canProceedToNextStep()
? 'bg-blue-600 text-white hover:bg-blue-700'
: 'bg-gray-200 text-gray-400 cursor-not-allowed'
? 'bg-blue-600 dark:bg-blue-500 text-white hover:bg-blue-700 dark:hover:bg-blue-600'
: 'bg-gray-200 dark:bg-gray-700 text-gray-400 dark:text-gray-500 cursor-not-allowed'
}`}
>
Next

View File

@@ -11,15 +11,15 @@ export default function ColorsStep({ config, updateConfig }) {
return (
<div>
<h2 className="text-2xl font-bold mb-4">Select Colors</h2>
<p className="text-gray-600 mb-6">
<h2 className="text-2xl font-bold mb-4 text-gray-900 dark:text-white">Select Colors</h2>
<p className="text-gray-600 dark:text-gray-300 mb-6">
Choose primary and accent colors for your OSSM build.
</p>
<div className="space-y-8">
{/* Primary Color */}
<div>
<h3 className="text-xl font-semibold mb-4">Primary Color</h3>
<h3 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Primary Color</h3>
<div className="grid grid-cols-3 md:grid-cols-6 gap-4">
{partsData.colors.primary.map((color) => (
<button
@@ -27,19 +27,19 @@ export default function ColorsStep({ config, updateConfig }) {
onClick={() => handlePrimaryColorSelect(color)}
className={`flex flex-col items-center p-4 border-2 rounded-lg transition-all ${
config.primaryColor === color.id
? 'border-blue-600 bg-blue-50'
: 'border-gray-200 hover:border-gray-300'
? 'border-blue-600 dark:border-blue-500 bg-blue-50 dark:bg-blue-900/30'
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
}`}
>
<div
className="w-16 h-16 rounded-full mb-2 border-2 border-gray-300"
className="w-16 h-16 rounded-full mb-2 border-2 border-gray-300 dark:border-gray-600"
style={{ backgroundColor: color.hex }}
/>
<span className="text-sm font-medium text-gray-700">
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
{color.name}
</span>
{config.primaryColor === color.id && (
<div className="mt-1 w-5 h-5 bg-blue-600 rounded-full flex items-center justify-center">
<div className="mt-1 w-5 h-5 bg-blue-600 dark:bg-blue-500 rounded-full flex items-center justify-center">
<svg
className="w-3 h-3 text-white"
fill="none"
@@ -62,7 +62,7 @@ export default function ColorsStep({ config, updateConfig }) {
{/* Accent Color */}
<div>
<h3 className="text-xl font-semibold mb-4">Accent Color</h3>
<h3 className="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Accent Color</h3>
<div className="grid grid-cols-3 md:grid-cols-6 gap-4">
{partsData.colors.accent.map((color) => (
<button
@@ -70,19 +70,19 @@ export default function ColorsStep({ config, updateConfig }) {
onClick={() => handleAccentColorSelect(color)}
className={`flex flex-col items-center p-4 border-2 rounded-lg transition-all ${
config.accentColor === color.id
? 'border-blue-600 bg-blue-50'
: 'border-gray-200 hover:border-gray-300'
? 'border-blue-600 dark:border-blue-500 bg-blue-50 dark:bg-blue-900/30'
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
}`}
>
<div
className="w-16 h-16 rounded-full mb-2 border-2 border-gray-300"
className="w-16 h-16 rounded-full mb-2 border-2 border-gray-300 dark:border-gray-600"
style={{ backgroundColor: color.hex }}
/>
<span className="text-sm font-medium text-gray-700">
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
{color.name}
</span>
{config.accentColor === color.id && (
<div className="mt-1 w-5 h-5 bg-blue-600 rounded-full flex items-center justify-center">
<div className="mt-1 w-5 h-5 bg-blue-600 dark:bg-blue-500 rounded-full flex items-center justify-center">
<svg
className="w-3 h-3 text-white"
fill="none"

View File

@@ -18,15 +18,15 @@ export default function MotorStep({ config, updateConfig }) {
onClick={() => handleSelect(motor)}
className={`${isSlightlyLarger ? 'p-5' : 'p-4'} border-2 rounded-lg text-left transition-all ${
selectedMotorId === motor.id
? 'border-blue-600 bg-blue-50 shadow-lg'
? 'border-blue-600 dark:border-blue-500 bg-blue-50 dark:bg-blue-900/30 shadow-lg'
: motor.recommended
? 'border-green-500 bg-green-50 hover:border-green-600 hover:bg-green-100'
: 'border-gray-200 hover:border-gray-300 hover:bg-gray-50'
? 'border-green-500 dark:border-green-600 bg-green-50 dark:bg-green-900/30 hover:border-green-600 dark:hover:border-green-500 hover:bg-green-100 dark:hover:bg-green-900/40'
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700/50'
}`}
>
{motor.recommended && (
<div className="mb-3 flex items-center gap-2">
<span className="inline-flex items-center px-3 py-1 text-xs font-semibold text-green-800 bg-green-200 rounded-full">
<span className="inline-flex items-center px-3 py-1 text-xs font-semibold text-green-800 dark:text-green-300 bg-green-200 dark:bg-green-900/50 rounded-full">
Recommended
</span>
</div>
@@ -36,7 +36,7 @@ export default function MotorStep({ config, updateConfig }) {
<img
src={motor.image}
alt={motor.name}
className={`${isSlightlyLarger ? 'h-32 w-32' : 'h-24 w-24'} object-contain rounded-lg bg-gray-100`}
className={`${isSlightlyLarger ? 'h-32 w-32' : 'h-24 w-24'} object-contain rounded-lg bg-gray-100 dark:bg-gray-700`}
onError={(e) => {
e.target.style.display = 'none';
}}
@@ -44,11 +44,11 @@ export default function MotorStep({ config, updateConfig }) {
</div>
)}
<div className="flex items-start justify-between mb-2">
<h3 className={`${isSlightlyLarger ? 'text-lg' : 'text-base'} font-semibold text-gray-900`}>
<h3 className={`${isSlightlyLarger ? 'text-lg' : 'text-base'} font-semibold text-gray-900 dark:text-white`}>
{motor.name}
</h3>
{selectedMotorId === motor.id && (
<div className="w-6 h-6 bg-blue-600 rounded-full flex items-center justify-center flex-shrink-0">
<div className="w-6 h-6 bg-blue-600 dark:bg-blue-500 rounded-full flex items-center justify-center flex-shrink-0">
<svg
className="w-4 h-4 text-white"
fill="none"
@@ -65,29 +65,29 @@ export default function MotorStep({ config, updateConfig }) {
</div>
)}
</div>
<p className={`${isSlightlyLarger ? 'text-sm' : 'text-sm'} text-gray-600 mb-3`}>{motor.description}</p>
<p className={`${isSlightlyLarger ? 'text-sm' : 'text-sm'} text-gray-600 dark:text-gray-300 mb-3`}>{motor.description}</p>
<div className={`flex ${isSlightlyLarger ? 'gap-4' : 'gap-3'} text-sm`}>
<div>
<span className="text-gray-500">Speed:</span>{' '}
<span className="font-medium">{motor.speed}</span>
<span className="text-gray-500 dark:text-gray-400">Speed:</span>{' '}
<span className="font-medium text-gray-900 dark:text-white">{motor.speed}</span>
</div>
<div>
<span className="text-gray-500">Wattage:</span>{' '}
<span className="font-medium">{motor.wattage}</span>
<span className="text-gray-500 dark:text-gray-400">Wattage:</span>{' '}
<span className="font-medium text-gray-900 dark:text-white">{motor.wattage}</span>
</div>
<div>
<span className="text-gray-500">Gear Count:</span>{' '}
<span className="font-medium">{motor.gear_count}</span>
<span className="text-gray-500 dark:text-gray-400">Gear Count:</span>{' '}
<span className="font-medium text-gray-900 dark:text-white">{motor.gear_count}</span>
</div>
</div>
<div className={`${isSlightlyLarger ? 'mt-3' : 'mt-2'} flex items-center justify-between`}>
<div className={`${isSlightlyLarger ? 'text-lg' : 'text-lg'} font-bold text-blue-600`}>
<div className={`${isSlightlyLarger ? 'text-lg' : 'text-lg'} font-bold text-blue-600 dark:text-blue-400`}>
{formatPrice(motor.price)}
</div>
</div>
{motor.links && motor.links.length > 0 && (
<div className={`${isSlightlyLarger ? 'mt-3' : 'mt-2'} pt-3 border-t border-gray-200`}>
<p className="text-xs text-gray-500 mb-2">Buy from:</p>
<div className={`${isSlightlyLarger ? 'mt-3' : 'mt-2'} pt-3 border-t border-gray-200 dark:border-gray-700`}>
<p className="text-xs text-gray-500 dark:text-gray-400 mb-2">Buy from:</p>
<div className="flex flex-wrap gap-2">
{motor.links.map((link, index) => (
<a
@@ -95,7 +95,7 @@ export default function MotorStep({ config, updateConfig }) {
href={link.link}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center px-3 py-1.5 text-xs font-medium text-blue-700 bg-blue-50 border border-blue-200 rounded-md hover:bg-blue-100 hover:text-blue-800 transition-colors"
className="inline-flex items-center px-3 py-1.5 text-xs font-medium text-blue-700 dark:text-blue-300 bg-blue-50 dark:bg-blue-900/30 border border-blue-200 dark:border-blue-800 rounded-md hover:bg-blue-100 dark:hover:bg-blue-900/50 hover:text-blue-800 dark:hover:text-blue-200 transition-colors"
onClick={(e) => e.stopPropagation()}
>
<svg
@@ -122,8 +122,8 @@ export default function MotorStep({ config, updateConfig }) {
return (
<div>
<h2 className="text-2xl font-bold mb-4">Select Motor</h2>
<p className="text-gray-600 mb-6">
<h2 className="text-2xl font-bold mb-4 text-gray-900 dark:text-white">Select Motor</h2>
<p className="text-gray-600 dark:text-gray-300 mb-6">
Choose the stepper motor for your OSSM build.
</p>
@@ -136,7 +136,7 @@ export default function MotorStep({ config, updateConfig }) {
</div>
) : (
<div>
<h3 className="text-lg font-semibold mb-4 text-gray-700">Recommended Options</h3>
<h3 className="text-lg font-semibold mb-4 text-gray-700 dark:text-gray-300">Recommended Options</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{recommendedMotors.map((motor) => renderMotorCard(motor, true, false))}
</div>
@@ -148,7 +148,7 @@ export default function MotorStep({ config, updateConfig }) {
{/* Other Motors - Smaller Grid */}
{otherMotors.length > 0 && (
<div>
<h3 className="text-lg font-semibold mb-4 text-gray-700">Other Options</h3>
<h3 className="text-lg font-semibold mb-4 text-gray-700 dark:text-gray-300">Other Options</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{otherMotors.map((motor) => renderMotorCard(motor, false, false))}
</div>

View File

@@ -169,8 +169,8 @@ export default function OptionsStep({ config, updateConfig, buildType }) {
onClick={() => handleOptionClick(option, mainSectionId, subSectionId)}
className={`p-4 border-2 rounded-lg text-left transition-all w-full ${
isSelected
? 'border-blue-600 bg-blue-50'
: 'border-gray-200 hover:border-gray-300 hover:bg-gray-50'
? 'border-blue-600 dark:border-blue-500 bg-blue-50 dark:bg-blue-900/30'
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700/50'
}`}
>
{option.image && (
@@ -178,7 +178,7 @@ export default function OptionsStep({ config, updateConfig, buildType }) {
<img
src={option.image}
alt={option.name}
className="h-48 w-48 object-contain rounded-lg bg-gray-100"
className="h-48 w-48 object-contain rounded-lg bg-gray-100 dark:bg-gray-700"
onError={(e) => {
e.target.style.display = 'none';
}}
@@ -187,15 +187,15 @@ export default function OptionsStep({ config, updateConfig, buildType }) {
)}
<div className="flex items-start justify-between mb-2">
<div className="flex-1">
<h4 className="font-semibold text-gray-900 mb-1">
<h4 className="font-semibold text-gray-900 dark:text-white mb-1">
{option.name}
</h4>
{option.description && (
<p className="text-sm text-gray-600">{option.description}</p>
<p className="text-sm text-gray-600 dark:text-gray-300">{option.description}</p>
)}
</div>
{isSelected && (
<div className="w-6 h-6 bg-blue-600 rounded-full flex items-center justify-center flex-shrink-0 ml-2">
<div className="w-6 h-6 bg-blue-600 dark:bg-blue-500 rounded-full flex items-center justify-center flex-shrink-0 ml-2">
{isMultiSelect ? (
<span className="text-white text-sm font-bold"></span>
) : (
@@ -219,14 +219,14 @@ export default function OptionsStep({ config, updateConfig, buildType }) {
<div className="flex flex-wrap gap-4 text-sm mt-3">
{option.filamentEstimate && (
<div>
<span className="text-gray-500">Filament:</span>{' '}
<span className="font-medium">{option.filamentEstimate}</span>
<span className="text-gray-500 dark:text-gray-400">Filament:</span>{' '}
<span className="font-medium text-gray-900 dark:text-white">{option.filamentEstimate}</span>
</div>
)}
{option.hardwareCost !== undefined && (
<div>
<span className="text-gray-500">Hardware:</span>{' '}
<span className="font-medium">
<span className="text-gray-500 dark:text-gray-400">Hardware:</span>{' '}
<span className="font-medium text-gray-900 dark:text-white">
{formatPrice(option.hardwareCost)}
</span>
</div>
@@ -243,16 +243,16 @@ export default function OptionsStep({ config, updateConfig, buildType }) {
const isExpanded = expandedSubSections[subSectionKey] !== false && (!hasSelection || expandedSubSections[subSectionKey] === true);
return (
<div key={subSectionId} className="border border-gray-200 rounded-lg overflow-hidden">
<div key={subSectionId} className="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
<button
onClick={() => toggleSubSection(subSectionKey)}
className="w-full px-4 py-3 bg-gray-50 hover:bg-gray-100 transition-colors flex items-center justify-between"
className="w-full px-4 py-3 bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors flex items-center justify-between"
>
<div className="flex items-center gap-3">
<h4 className="font-semibold text-gray-800">{subSection.title}</h4>
<h4 className="font-semibold text-gray-800 dark:text-gray-200">{subSection.title}</h4>
{hasSelection && (
<div className="flex items-center gap-2">
<div className="w-4 h-4 bg-green-500 rounded-full flex items-center justify-center">
<div className="w-4 h-4 bg-green-500 dark:bg-green-600 rounded-full flex items-center justify-center">
<svg
className="w-2.5 h-2.5 text-white"
fill="none"
@@ -267,14 +267,14 @@ export default function OptionsStep({ config, updateConfig, buildType }) {
/>
</svg>
</div>
<span className="text-sm text-gray-600">
<span className="text-sm text-gray-600 dark:text-gray-300">
{selectedOptions.map((opt) => opt.name).join(', ')}
</span>
</div>
)}
</div>
<svg
className={`w-4 h-4 text-gray-500 transition-transform ${
className={`w-4 h-4 text-gray-500 dark:text-gray-400 transition-transform ${
isExpanded ? 'transform rotate-180' : ''
}`}
fill="none"
@@ -290,7 +290,7 @@ export default function OptionsStep({ config, updateConfig, buildType }) {
</svg>
</button>
{isExpanded && subSection.options && subSection.options.length > 0 && (
<div className="p-4 bg-white">
<div className="p-4 bg-white dark:bg-gray-800">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{subSection.options.map((option) =>
renderOptionCard(option, mainSectionId, subSectionId, subSection, subSection.isMultiSelect)
@@ -309,25 +309,25 @@ export default function OptionsStep({ config, updateConfig, buildType }) {
return (
<div key={mainSectionId} className={`border-2 rounded-lg overflow-hidden mb-4 ${
isComplete ? 'border-green-500' : 'border-gray-300'
isComplete ? 'border-green-500 dark:border-green-600' : 'border-gray-300 dark:border-gray-700'
}`}>
<button
onClick={() => toggleMainSection(mainSectionId)}
className={`w-full px-6 py-4 transition-colors flex items-center justify-between ${
isComplete
? 'bg-green-50 hover:bg-green-100'
: 'bg-gray-100 hover:bg-gray-200'
? 'bg-green-50 dark:bg-green-900/30 hover:bg-green-100 dark:hover:bg-green-900/40'
: 'bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700'
}`}
>
<div className="flex items-center gap-3">
<h3 className={`text-xl font-bold ${
isComplete ? 'text-green-900' : 'text-gray-900'
isComplete ? 'text-green-900 dark:text-green-300' : 'text-gray-900 dark:text-white'
}`}>
{mainSection.title}
</h3>
{isComplete && (
<div className="flex items-center gap-2">
<div className="w-6 h-6 bg-green-500 rounded-full flex items-center justify-center">
<div className="w-6 h-6 bg-green-500 dark:bg-green-600 rounded-full flex items-center justify-center">
<svg
className="w-4 h-4 text-white"
fill="none"
@@ -342,14 +342,14 @@ export default function OptionsStep({ config, updateConfig, buildType }) {
/>
</svg>
</div>
<span className="text-sm font-medium text-green-700">Complete</span>
<span className="text-sm font-medium text-green-700 dark:text-green-300">Complete</span>
</div>
)}
</div>
<svg
className={`w-6 h-6 transition-transform ${
isExpanded ? 'transform rotate-180' : ''
} ${isComplete ? 'text-green-600' : 'text-gray-600'}`}
} ${isComplete ? 'text-green-600 dark:text-green-400' : 'text-gray-600 dark:text-gray-400'}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
@@ -363,7 +363,7 @@ export default function OptionsStep({ config, updateConfig, buildType }) {
</svg>
</button>
{isExpanded && (
<div className="p-4 space-y-4 bg-white">
<div className="p-4 space-y-4 bg-white dark:bg-gray-800">
{subSections.map(([subSectionId, subSection]) =>
renderSubSection(mainSectionId, subSectionId, subSection)
)}
@@ -419,18 +419,18 @@ export default function OptionsStep({ config, updateConfig, buildType }) {
return (
<div>
<h2 className="text-2xl font-bold mb-4">
<h2 className="text-2xl font-bold mb-4 text-gray-900 dark:text-white">
{buildType === 'upgrade' ? 'Select Upgrades & Modifications' : 'Select Options'}
</h2>
<p className="text-gray-600 mb-6">
<p className="text-gray-600 dark:text-gray-300 mb-6">
{buildType === 'upgrade'
? 'Choose upgrade components and modifications for your existing build.'
: 'Choose your preferred mounting options and accessories.'}
</p>
{sectionsToRender.length === 0 && buildType === 'upgrade' && (
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-6">
<p className="text-yellow-800">
<div className="bg-yellow-50 dark:bg-yellow-900/30 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4 mb-6">
<p className="text-yellow-800 dark:text-yellow-300">
No upgrade components available. All components are base modules.
</p>
</div>

View File

@@ -17,14 +17,14 @@ export default function PowerSupplyStep({ config, updateConfig }) {
return (
<div>
<h2 className="text-2xl font-bold mb-4">Select Power Supply</h2>
<p className="text-gray-600 mb-6">
<h2 className="text-2xl font-bold mb-4 text-gray-900 dark:text-white">Select Power Supply</h2>
<p className="text-gray-600 dark:text-gray-300 mb-6">
Choose a compatible power supply for your selected motor.
</p>
{selectedMotorId && (
<div className="mb-4 p-4 bg-blue-50 border border-blue-200 rounded-lg">
<p className="text-sm text-blue-800">
<div className="mb-4 p-4 bg-blue-50 dark:bg-blue-900/30 border border-blue-200 dark:border-blue-800 rounded-lg">
<p className="text-sm text-blue-800 dark:text-blue-300">
Showing power supplies compatible with:{' '}
<span className="font-semibold">
{config.motor?.name || 'Selected Motor'}
@@ -40,8 +40,8 @@ export default function PowerSupplyStep({ config, updateConfig }) {
onClick={() => handleSelect(powerSupply)}
className={`p-6 border-2 rounded-lg text-left transition-all ${
selectedPowerSupplyId === powerSupply.id
? 'border-blue-600 bg-blue-50'
: 'border-gray-200 hover:border-gray-300 hover:bg-gray-50'
? 'border-blue-600 dark:border-blue-500 bg-blue-50 dark:bg-blue-900/30'
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700/50'
}`}
>
{powerSupply.image && (
@@ -49,7 +49,7 @@ export default function PowerSupplyStep({ config, updateConfig }) {
<img
src={powerSupply.image}
alt={powerSupply.name}
className="h-32 w-32 object-contain rounded-lg bg-gray-100"
className="h-32 w-32 object-contain rounded-lg bg-gray-100 dark:bg-gray-700"
onError={(e) => {
e.target.style.display = 'none';
}}
@@ -57,11 +57,11 @@ export default function PowerSupplyStep({ config, updateConfig }) {
</div>
)}
<div className="flex items-start justify-between mb-2">
<h3 className="text-lg font-semibold text-gray-900">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
{powerSupply.name}
</h3>
{selectedPowerSupplyId === powerSupply.id && (
<div className="w-6 h-6 bg-blue-600 rounded-full flex items-center justify-center flex-shrink-0">
<div className="w-6 h-6 bg-blue-600 dark:bg-blue-500 rounded-full flex items-center justify-center flex-shrink-0">
<svg
className="w-4 h-4 text-white"
fill="none"
@@ -78,27 +78,27 @@ export default function PowerSupplyStep({ config, updateConfig }) {
</div>
)}
</div>
<p className="text-sm text-gray-600 mb-3">
<p className="text-sm text-gray-600 dark:text-gray-300 mb-3">
{powerSupply.description}
</p>
<div className="flex gap-4 text-sm">
<div>
<span className="text-gray-500">Voltage:</span>{' '}
<span className="font-medium">{powerSupply.voltage}</span>
<span className="text-gray-500 dark:text-gray-400">Voltage:</span>{' '}
<span className="font-medium text-gray-900 dark:text-white">{powerSupply.voltage}</span>
</div>
<div>
<span className="text-gray-500">Current:</span>{' '}
<span className="font-medium">{powerSupply.current}</span>
<span className="text-gray-500 dark:text-gray-400">Current:</span>{' '}
<span className="font-medium text-gray-900 dark:text-white">{powerSupply.current}</span>
</div>
</div>
<div className="mt-3 flex items-center justify-between">
<div className="text-lg font-bold text-blue-600">
<div className="text-lg font-bold text-blue-600 dark:text-blue-400">
{formatPrice(powerSupply.price)}
</div>
</div>
{powerSupply.links && powerSupply.links.length > 0 && (
<div className="mt-3 pt-3 border-t border-gray-200">
<p className="text-xs text-gray-500 mb-2">Buy from:</p>
<div className="mt-3 pt-3 border-t border-gray-200 dark:border-gray-700">
<p className="text-xs text-gray-500 dark:text-gray-400 mb-2">Buy from:</p>
<div className="flex flex-wrap gap-2">
{powerSupply.links.map((link, index) => (
<a
@@ -106,7 +106,7 @@ export default function PowerSupplyStep({ config, updateConfig }) {
href={link.link}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center px-3 py-1.5 text-xs font-medium text-blue-700 bg-blue-50 border border-blue-200 rounded-md hover:bg-blue-100 hover:text-blue-800 transition-colors"
className="inline-flex items-center px-3 py-1.5 text-xs font-medium text-blue-700 dark:text-blue-300 bg-blue-50 dark:bg-blue-900/30 border border-blue-200 dark:border-blue-800 rounded-md hover:bg-blue-100 dark:hover:bg-blue-900/50 hover:text-blue-800 dark:hover:text-blue-200 transition-colors"
onClick={(e) => e.stopPropagation()}
>
<svg

View File

@@ -90,8 +90,8 @@ export default function RemoteStep({ config, updateConfig, buildType }) {
onClick={() => handleRemoteSelect(remote.id)}
className={`p-4 border-2 rounded-lg text-left transition-all w-full ${
isSelected
? 'border-blue-600 bg-blue-50'
: 'border-gray-200 hover:border-gray-300 hover:bg-gray-50'
? 'border-blue-600 dark:border-blue-500 bg-blue-50 dark:bg-blue-900/30'
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700/50'
}`}
>
{imagePath && (
@@ -99,7 +99,7 @@ export default function RemoteStep({ config, updateConfig, buildType }) {
<img
src={imagePath}
alt={remote.name}
className="h-48 w-48 object-contain rounded-lg bg-gray-100"
className="h-48 w-48 object-contain rounded-lg bg-gray-100 dark:bg-gray-700"
onError={(e) => {
e.target.style.display = 'none';
}}
@@ -108,13 +108,13 @@ export default function RemoteStep({ config, updateConfig, buildType }) {
)}
<div className="flex items-start justify-between mb-2">
<div className="flex-1">
<h4 className="font-semibold text-gray-900 mb-1">
<h4 className="font-semibold text-gray-900 dark:text-white mb-1">
{remote.name}
</h4>
<p className="text-sm text-gray-600">{remote.description}</p>
<p className="text-sm text-gray-600 dark:text-gray-300">{remote.description}</p>
</div>
{isSelected && (
<div className="w-6 h-6 bg-blue-600 rounded-full flex items-center justify-center flex-shrink-0 ml-2">
<div className="w-6 h-6 bg-blue-600 dark:bg-blue-500 rounded-full flex items-center justify-center flex-shrink-0 ml-2">
<svg
className="w-4 h-4 text-white"
fill="none"
@@ -143,14 +143,14 @@ export default function RemoteStep({ config, updateConfig, buildType }) {
return (
<div className="mt-4 mb-6">
<h3 className="text-lg font-semibold mb-3">PCB Purchase Source</h3>
<h3 className="text-lg font-semibold mb-3 text-gray-900 dark:text-white">PCB Purchase Source</h3>
<div className="flex gap-4">
<button
onClick={() => handlePCBSelect('rad')}
className={`px-4 py-2 border-2 rounded-lg transition-all ${
selectedRemotePCB === 'rad'
? 'border-blue-600 bg-blue-50 text-blue-900 font-medium'
: 'border-gray-200 hover:border-gray-300'
? 'border-blue-600 dark:border-blue-500 bg-blue-50 dark:bg-blue-900/30 text-blue-900 dark:text-blue-300 font-medium'
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
}`}
>
Purchase from RAD
@@ -159,8 +159,8 @@ export default function RemoteStep({ config, updateConfig, buildType }) {
onClick={() => handlePCBSelect('pcbway')}
className={`px-4 py-2 border-2 rounded-lg transition-all ${
selectedRemotePCB === 'pcbway'
? 'border-blue-600 bg-blue-50 text-blue-900 font-medium'
: 'border-gray-200 hover:border-gray-300'
? 'border-blue-600 dark:border-blue-500 bg-blue-50 dark:bg-blue-900/30 text-blue-900 dark:text-blue-300 font-medium'
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
}`}
>
Self-source with PCBWay
@@ -179,21 +179,21 @@ export default function RemoteStep({ config, updateConfig, buildType }) {
onClick={() => handleKnobSelect(knob)}
className={`p-4 border-2 rounded-lg text-left transition-all w-full ${
isSelected
? 'border-blue-600 bg-blue-50'
: 'border-gray-200 hover:border-gray-300 hover:bg-gray-50'
? 'border-blue-600 dark:border-blue-500 bg-blue-50 dark:bg-blue-900/30'
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700/50'
}`}
>
<div className="flex items-start justify-between mb-2">
<div className="flex-1">
<h4 className="font-semibold text-gray-900 mb-1">
<h4 className="font-semibold text-gray-900 dark:text-white mb-1">
{knob.name}
</h4>
{knob.description && (
<p className="text-sm text-gray-600">{knob.description}</p>
<p className="text-sm text-gray-600 dark:text-gray-300">{knob.description}</p>
)}
</div>
{isSelected && (
<div className="w-6 h-6 bg-blue-600 rounded-full flex items-center justify-center flex-shrink-0 ml-2">
<div className="w-6 h-6 bg-blue-600 dark:bg-blue-500 rounded-full flex items-center justify-center flex-shrink-0 ml-2">
<svg
className="w-4 h-4 text-white"
fill="none"
@@ -213,8 +213,8 @@ export default function RemoteStep({ config, updateConfig, buildType }) {
<div className="flex flex-wrap gap-4 text-sm mt-3">
{knob.filamentEstimate && (
<div>
<span className="text-gray-500">Filament:</span>{' '}
<span className="font-medium">{knob.filamentEstimate}</span>
<span className="text-gray-500 dark:text-gray-400">Filament:</span>{' '}
<span className="font-medium text-gray-900 dark:text-white">{knob.filamentEstimate}</span>
</div>
)}
</div>
@@ -236,8 +236,8 @@ export default function RemoteStep({ config, updateConfig, buildType }) {
return (
<div>
<h2 className="text-2xl font-bold mb-4">Select Remote Control</h2>
<p className="text-gray-600 mb-6">
<h2 className="text-2xl font-bold mb-4 text-gray-900 dark:text-white">Select Remote Control</h2>
<p className="text-gray-600 dark:text-gray-300 mb-6">
Choose your remote control system and knob option.
</p>
@@ -254,16 +254,16 @@ export default function RemoteStep({ config, updateConfig, buildType }) {
{/* Knobs Selection (only shown when remote is selected) */}
{hasRemoteSelected && availableKnobs.length > 0 && (
<div className="border border-gray-200 rounded-lg overflow-hidden">
<div className="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
<button
onClick={() => setExpandedKnobs(!expandedKnobs)}
className="w-full px-4 py-3 bg-gray-50 hover:bg-gray-100 transition-colors flex items-center justify-between"
className="w-full px-4 py-3 bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors flex items-center justify-between"
>
<div className="flex items-center gap-3">
<h3 className="text-lg font-semibold text-gray-800">Remote Knobs</h3>
<h3 className="text-lg font-semibold text-gray-800 dark:text-gray-200">Remote Knobs</h3>
{hasKnobSelected && (
<div className="flex items-center gap-2">
<div className="w-4 h-4 bg-green-500 rounded-full flex items-center justify-center">
<div className="w-4 h-4 bg-green-500 dark:bg-green-600 rounded-full flex items-center justify-center">
<svg
className="w-2.5 h-2.5 text-white"
fill="none"
@@ -278,14 +278,14 @@ export default function RemoteStep({ config, updateConfig, buildType }) {
/>
</svg>
</div>
<span className="text-sm text-gray-600">
<span className="text-sm text-gray-600 dark:text-gray-300">
{config.remoteKnob?.name}
</span>
</div>
)}
</div>
<svg
className={`w-4 h-4 text-gray-500 transition-transform ${
className={`w-4 h-4 text-gray-500 dark:text-gray-400 transition-transform ${
expandedKnobs ? 'transform rotate-180' : ''
}`}
fill="none"
@@ -301,7 +301,7 @@ export default function RemoteStep({ config, updateConfig, buildType }) {
</svg>
</button>
{expandedKnobs && (
<div className="p-4 bg-white">
<div className="p-4 bg-white dark:bg-gray-800">
<div className="grid grid-cols-2 lg:grid-cols-3 gap-4">
{availableKnobs.map((knob) => renderKnobCard(knob))}
</div>
@@ -311,8 +311,8 @@ export default function RemoteStep({ config, updateConfig, buildType }) {
)}
{!hasRemoteSelected && (
<div className="mt-6 bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<p className="text-yellow-800 text-sm">
<div className="mt-6 bg-yellow-50 dark:bg-yellow-900/30 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4">
<p className="text-yellow-800 dark:text-yellow-300 text-sm">
<strong>Note:</strong> Please select a remote control system to continue.
</p>
</div>

View File

@@ -51,8 +51,8 @@ export default function ToyMountStep({ config, updateConfig }) {
onClick={() => handleOptionClick(option, subSectionId, subSection)}
className={`p-4 border-2 rounded-lg text-left transition-all w-full ${
isSelected
? 'border-blue-600 bg-blue-50'
: 'border-gray-200 hover:border-gray-300 hover:bg-gray-50'
? 'border-blue-600 dark:border-blue-500 bg-blue-50 dark:bg-blue-900/30'
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700/50'
}`}
>
<div className="flex items-start gap-4">
@@ -61,7 +61,7 @@ export default function ToyMountStep({ config, updateConfig }) {
<img
src={option.image}
alt={option.name}
className="h-24 w-24 object-contain rounded-lg bg-gray-100"
className="h-24 w-24 object-contain rounded-lg bg-gray-100 dark:bg-gray-700"
onError={(e) => {
e.target.style.display = 'none';
}}
@@ -71,15 +71,15 @@ export default function ToyMountStep({ config, updateConfig }) {
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between mb-2">
<div className="flex-1">
<h4 className="font-semibold text-gray-900 mb-1">
<h4 className="font-semibold text-gray-900 dark:text-white mb-1">
{option.name}
</h4>
{option.description && (
<p className="text-sm text-gray-600">{option.description}</p>
<p className="text-sm text-gray-600 dark:text-gray-300">{option.description}</p>
)}
</div>
{isSelected && (
<div className="w-6 h-6 bg-blue-600 rounded-full flex items-center justify-center flex-shrink-0 ml-2">
<div className="w-6 h-6 bg-blue-600 dark:bg-blue-500 rounded-full flex items-center justify-center flex-shrink-0 ml-2">
<span className="text-white text-sm font-bold"></span>
</div>
)}
@@ -87,14 +87,14 @@ export default function ToyMountStep({ config, updateConfig }) {
<div className="flex flex-wrap gap-4 text-sm mt-3">
{option.filamentEstimate && (
<div>
<span className="text-gray-500">Filament:</span>{' '}
<span className="font-medium">{option.filamentEstimate}</span>
<span className="text-gray-500 dark:text-gray-400">Filament:</span>{' '}
<span className="font-medium text-gray-900 dark:text-white">{option.filamentEstimate}</span>
</div>
)}
{option.hardwareCost !== undefined && (
<div>
<span className="text-gray-500">Hardware:</span>{' '}
<span className="font-medium">
<span className="text-gray-500 dark:text-gray-400">Hardware:</span>{' '}
<span className="font-medium text-gray-900 dark:text-white">
{formatPrice(option.hardwareCost)}
</span>
</div>
@@ -113,16 +113,16 @@ export default function ToyMountStep({ config, updateConfig }) {
const isExpanded = expandedSubSections[subSectionKey] !== false && (!hasSelection || expandedSubSections[subSectionKey] === true);
return (
<div key={subSectionId} className="border border-gray-200 rounded-lg overflow-hidden">
<div key={subSectionId} className="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
<button
onClick={() => toggleSubSection(subSectionKey)}
className="w-full px-4 py-3 bg-gray-50 hover:bg-gray-100 transition-colors flex items-center justify-between"
className="w-full px-4 py-3 bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors flex items-center justify-between"
>
<div className="flex items-center gap-3">
<h4 className="font-semibold text-gray-800">{subSection.title}</h4>
<h4 className="font-semibold text-gray-800 dark:text-gray-200">{subSection.title}</h4>
{hasSelection && (
<div className="flex items-center gap-2">
<div className="w-4 h-4 bg-green-500 rounded-full flex items-center justify-center">
<div className="w-4 h-4 bg-green-500 dark:bg-green-600 rounded-full flex items-center justify-center">
<svg
className="w-2.5 h-2.5 text-white"
fill="none"
@@ -137,14 +137,14 @@ export default function ToyMountStep({ config, updateConfig }) {
/>
</svg>
</div>
<span className="text-sm text-gray-600">
<span className="text-sm text-gray-600 dark:text-gray-300">
{selectedOptions.map((opt) => opt.name).join(', ')}
</span>
</div>
)}
</div>
<svg
className={`w-4 h-4 text-gray-500 transition-transform ${
className={`w-4 h-4 text-gray-500 dark:text-gray-400 transition-transform ${
isExpanded ? 'transform rotate-180' : ''
}`}
fill="none"
@@ -160,7 +160,7 @@ export default function ToyMountStep({ config, updateConfig }) {
</svg>
</button>
{isExpanded && subSection.options && subSection.options.length > 0 && (
<div className="p-4 space-y-3 bg-white">
<div className="p-4 space-y-3 bg-white dark:bg-gray-800">
{subSection.options.map((option) =>
renderOptionCard(option, subSectionId, subSection)
)}
@@ -176,8 +176,8 @@ export default function ToyMountStep({ config, updateConfig }) {
return (
<div>
<h2 className="text-2xl font-bold mb-4">Select Toy Mounts</h2>
<p className="text-gray-600 mb-6">
<h2 className="text-2xl font-bold mb-4 text-gray-900 dark:text-white">Select Toy Mounts</h2>
<p className="text-gray-600 dark:text-gray-300 mb-6">
Choose your preferred toy mount options. You can select multiple options from different categories.
</p>
@@ -190,8 +190,8 @@ export default function ToyMountStep({ config, updateConfig }) {
)}
{!hasSelection && (
<div className="mt-6 bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<p className="text-yellow-800 text-sm">
<div className="mt-6 bg-yellow-50 dark:bg-yellow-900/30 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4">
<p className="text-yellow-800 dark:text-yellow-300 text-sm">
<strong>Note:</strong> At least one toy mount option is recommended for your build.
</p>
</div>

View File

@@ -0,0 +1,75 @@
import { createContext, useContext, useState, useEffect } from 'react';
const ThemeContext = createContext();
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
};
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState(() => {
// Check localStorage first
if (typeof window !== 'undefined') {
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'light' || savedTheme === 'dark') {
return savedTheme;
}
// Check system preference only if no saved preference
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
}
return 'light';
});
useEffect(() => {
// Apply theme to document
if (typeof document !== 'undefined') {
if (theme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}
// Save to localStorage
if (typeof window !== 'undefined') {
localStorage.setItem('theme', theme);
}
}, [theme]);
// Listen for system theme changes
useEffect(() => {
if (typeof window === 'undefined') return;
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleChange = (e) => {
// Only update if user hasn't manually set a preference
// If theme is saved in localStorage, it means user has manually set it
const savedTheme = localStorage.getItem('theme');
if (!savedTheme || savedTheme === 'null' || savedTheme === 'undefined') {
setTheme(e.matches ? 'dark' : 'light');
}
};
mediaQuery.addEventListener('change', handleChange);
return () => mediaQuery.removeEventListener('change', handleChange);
}, []);
const toggleTheme = () => {
setTheme((prevTheme) => {
const newTheme = prevTheme === 'light' ? 'dark' : 'light';
// Immediately save to localStorage to prevent system preference from overriding
localStorage.setItem('theme', newTheme);
return newTheme;
});
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme, setTheme }}>
{children}
</ThemeContext.Provider>
);
};

View File

@@ -0,0 +1,75 @@
{
"3030-mount": {
"category": "PCB Mount",
"type": "base",
"printedParts": [
{
"id": "ossm-pcb-3030-mount",
"name": "PCB 3030 Mount",
"description": "PCB mount for 3030 extrusion",
"filamentEstimate": 15,
"timeEstimate": "45m",
"colour": "primary",
"required": true,
"filePath": "OSSM - PCB - 3030 Mount.stl",
"url": "https://github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/PCB/OSSM%20-%20PCB%20-%203030%20Mount.stl?raw=true"
},
{
"id": "ossm-pcb-3030-mount-cover",
"name": "PCB 3030 Mount Cover",
"description": "Cover for the 3030 mount",
"filamentEstimate": 15,
"timeEstimate": "45m",
"colour": "primary",
"required": true,
"filePath": "OSSM - PCB - 3030 Mount Cover.stl",
"url": "https://github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/PCB/OSSM%20-%20PCB%20-%203030%20Mount%20Cover.stl?raw=true"
}
],
"hardwareParts": [
{
"id": "hardware-fasteners-m6x12-shcs",
"required": true,
"quantity": 4,
"relatedParts": [
"ossm-pcb-3030-mount"
]
},
{
"id": "hardware-fasteners-m6-t-nuts",
"required": true,
"quantity": 4,
"relatedParts": [
"ossm-pcb-3030-mount"
]
}
]
},
"aio-cover-mount": {
"category": "PCB Mount",
"type": "base",
"printedParts": [
{
"id": "ossm-pcb-aio-cover-mount",
"name": "PCB AIO Cover Mount",
"description": "All-in-one cover mount on the actuator",
"filamentEstimate": 20,
"timeEstimate": "1h",
"colour": "primary",
"required": true,
"filePath": "OSSM - PCB - AIO Cover Mount.stl",
"url": "https://github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/PCB/OSSM%20-%20PCB%20-%20AIO%20Cover%20Mount.stl?raw=true"
}
],
"hardwareParts": [
{
"id": "hardware-fasteners-m3x8-shcs",
"required": true,
"quantity": 4,
"relatedParts": [
"ossm-pcb-aio-cover-mount"
]
}
]
}
}

View File

@@ -18,7 +18,8 @@
"colour": "primary",
"required": true,
"filePath": "OSSM - Stand - Pivot Plate.stl",
"url": "https://github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/Stand/OSSM%20-%20Stand%20-%203030%20Extrusion%20Base%20-%20Pivot%20Plate%20Left.stl?raw=true"
"url": "https://github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/Stand/OSSM%20-%20Stand%20-%203030%20Extrusion%20Base%20-%20Pivot%20Plate%20Left.stl?raw=true",
"quantity": 1
},
{
"id": "pivot-plate-right",
@@ -28,7 +29,19 @@
"colour": "primary",
"required": true,
"filePath": "OSSM - Stand - Pivot Plate.stl",
"url": "https://github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/Stand/OSSM%20-%20Stand%20-%203030%20Extrusion%20Base%20-%20Pivot%20Plate%20Right.stl?raw=true"
"url": "https://github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/Stand/OSSM%20-%20Stand%20-%203030%20Extrusion%20Base%20-%20Pivot%20Plate%20Right.stl?raw=true",
"quantity": 1
},
{
"id": "handle-spacer",
"name": "Handle Spacer",
"description": "Handle spacer for the stand",
"filamentEstimate": 20,
"colour": "primary",
"required": true,
"filePath": "OSSM - Stand - Pivot Spacer.stl",
"url": "https://github.com/KinkyMakers/OSSM-hardware/blob/main/Printed%20Parts/Stand/OSSM%20-%20Stand%20-%203030%20Extrusion%20Base%20-%20Handle%20Spacer.stl?raw=true",
"quantity": 8
}
],
"hardwareParts": [
@@ -67,7 +80,8 @@
"description": "Reinforced 3030 hinges for PitClamp",
"filamentEstimate": 200,
"colour": "primary",
"required": true
"required": true,
"quantity": 1
}
],
"hardwareParts": [

View File

@@ -8,6 +8,7 @@ import standComponents from './components/stand.json';
import mountingComponents from './components/mounting.json';
import toyMountsComponents from './components/toyMounts.json';
import remoteComponents from './components/remote.json';
import pcbComponents from './components/pcb.json';
// Create a hardware lookup map from hardware.json
const hardwareLookup = new Map();
@@ -92,6 +93,7 @@ const rawComponents = {
...mountingComponents,
...toyMountsComponents,
...remoteComponents,
...pcbComponents,
};
// Resolve hardware references

View File

@@ -2,9 +2,12 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
import { ThemeProvider } from './contexts/ThemeContext'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
<ThemeProvider>
<App />
</ThemeProvider>
</StrictMode>,
)

View File

@@ -4,6 +4,7 @@ export default {
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
darkMode: 'class',
theme: {
extend: {},
},