writing1 / index.html
arirajuns's picture
Add 2 files
9b3ff43 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Software Design Principles for Novel Writing</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 2px;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
.flow-chart rect {
fill: #f0f9ff;
stroke: #0284c7;
stroke-width: 2px;
rx: 5;
}
.flow-chart text {
font: 12px sans-serif;
fill: #0369a1;
}
.flow-chart path {
fill: none;
stroke: #0284c7;
stroke-width: 2px;
}
.principle-card {
transition: all 0.3s ease;
}
.principle-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}
.tooltip {
position: absolute;
padding: 8px;
background: rgba(0, 0, 0, 0.8);
color: white;
border-radius: 4px;
pointer-events: none;
font-size: 12px;
max-width: 200px;
}
</style>
</head>
<body class="bg-gray-50 font-sans">
<div class="container mx-auto px-4 py-12">
<header class="text-center mb-16">
<h1 class="text-4xl font-bold text-blue-900 mb-4">Software Design Principles for Novel Writing</h1>
<p class="text-lg text-gray-600 max-w-3xl mx-auto">
Applying solid software engineering principles to the craft of writing novels can lead to more structured,
maintainable, and engaging stories.
</p>
</header>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-16">
<!-- Principle Cards -->
<div class="principle-card bg-white p-6 rounded-lg shadow-md border-l-4 border-blue-500">
<h3 class="text-xl font-semibold text-blue-800 mb-3">Single Responsibility</h3>
<p class="text-gray-600 mb-4">
Each character should have one clear purpose or role in the story, just like a class should have one responsibility.
</p>
<div id="srp-chart" class="h-40"></div>
</div>
<div class="principle-card bg-white p-6 rounded-lg shadow-md border-l-4 border-green-500">
<h3 class="text-xl font-semibold text-green-800 mb-3">Open/Closed Principle</h3>
<p class="text-gray-600 mb-4">
Your story structure should be open for extension (new plot lines) but closed for modification (core themes remain unchanged).
</p>
<div id="ocp-chart" class="h-40"></div>
</div>
<div class="principle-card bg-white p-6 rounded-lg shadow-md border-l-4 border-purple-500">
<h3 class="text-xl font-semibold text-purple-800 mb-3">Liskov Substitution</h3>
<p class="text-gray-600 mb-4">
Character archetypes should be substitutable - a mentor character should fulfill the same narrative role whether young or old.
</p>
<div id="lsp-chart" class="h-40"></div>
</div>
<div class="principle-card bg-white p-6 rounded-lg shadow-md border-l-4 border-yellow-500">
<h3 class="text-xl font-semibold text-yellow-800 mb-3">Interface Segregation</h3>
<p class="text-gray-600 mb-4">
Don't force readers to engage with unnecessary subplots. Keep narrative interfaces focused and relevant.
</p>
<div id="isp-chart" class="h-40"></div>
</div>
<div class="principle-card bg-white p-6 rounded-lg shadow-md border-l-4 border-red-500">
<h3 class="text-xl font-semibold text-red-800 mb-3">Dependency Inversion</h3>
<p class="text-gray-600 mb-4">
High-level themes should not depend on low-level plot details. Both should depend on abstractions (universal human experiences).
</p>
<div id="dip-chart" class="h-40"></div>
</div>
<div class="principle-card bg-white p-6 rounded-lg shadow-md border-l-4 border-indigo-500">
<h3 class="text-xl font-semibold text-indigo-800 mb-3">DRY Principle</h3>
<p class="text-gray-600 mb-4">
Avoid repetitive descriptions and redundant scenes. Each element should have a unique purpose in advancing the story.
</p>
<div id="dry-chart" class="h-40"></div>
</div>
</div>
<section class="bg-white rounded-lg shadow-md p-8 mb-16">
<h2 class="text-2xl font-bold text-blue-900 mb-6">Novel Writing Workflow</h2>
<div id="workflow-chart" class="h-96"></div>
</section>
<section class="bg-white rounded-lg shadow-md p-8 mb-16">
<h2 class="text-2xl font-bold text-blue-900 mb-6">Character Dependency Graph</h2>
<p class="text-gray-600 mb-6">
How characters interact in a story mirrors how objects interact in software. Hover over nodes to see details.
</p>
<div id="character-graph" class="h-96 border rounded-lg bg-gray-50"></div>
</section>
<section class="bg-white rounded-lg shadow-md p-8">
<h2 class="text-2xl font-bold text-blue-900 mb-6">Plot Structure Visualization</h2>
<p class="text-gray-600 mb-6">
A well-structured plot follows patterns similar to well-designed software architecture.
</p>
<div id="plot-structure" class="h-96"></div>
</section>
</div>
<div class="tooltip" style="opacity: 0;"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Single Responsibility Principle Chart
const srpData = [
{principle: "Single Responsibility", character: "Protagonist", responsibility: "Drive main plot"},
{principle: "Single Responsibility", character: "Sidekick", responsibility: "Provide comic relief"},
{principle: "Single Responsibility", character: "Antagonist", responsibility: "Create conflict"},
{principle: "Single Responsibility", character: "Mentor", responsibility: "Provide wisdom"}
];
const srpSvg = d3.select("#srp-chart")
.append("svg")
.attr("width", "100%")
.attr("height", "100%");
srpSvg.selectAll("circle")
.data(srpData)
.enter()
.append("circle")
.attr("cx", (d, i) => 50 + i * 70)
.attr("cy", 70)
.attr("r", 25)
.attr("fill", "#93c5fd")
.attr("stroke", "#1e40af");
srpSvg.selectAll("text")
.data(srpData)
.enter()
.append("text")
.text(d => d.character.charAt(0))
.attr("x", (d, i) => 50 + i * 70)
.attr("y", 75)
.attr("text-anchor", "middle")
.attr("fill", "#1e40af")
.attr("font-weight", "bold");
// Open/Closed Principle Chart
const ocpSvg = d3.select("#ocp-chart")
.append("svg")
.attr("width", "100%")
.attr("height", "100%");
ocpSvg.append("rect")
.attr("x", 30)
.attr("y", 30)
.attr("width", 120)
.attr("height", 80)
.attr("fill", "#a7f3d0")
.attr("stroke", "#065f46");
ocpSvg.append("text")
.text("Core Theme")
.attr("x", 90)
.attr("y", 70)
.attr("text-anchor", "middle")
.attr("fill", "#065f46");
ocpSvg.selectAll("circle")
.data([1, 2, 3])
.enter()
.append("circle")
.attr("cx", d => 30 + d * 30)
.attr("cy", 120)
.attr("r", 10)
.attr("fill", "#6ee7b7")
.attr("stroke", "#065f46");
// Liskov Substitution Chart
const lspSvg = d3.select("#lsp-chart")
.append("svg")
.attr("width", "100%")
.attr("height", "100%");
const mentorData = [
{type: "Wise Old Wizard", traits: "Magic, Experience"},
{type: "Retired Warrior", traits: "Combat, Strategy"},
{type: "Scholar", traits: "Knowledge, Research"}
];
lspSvg.selectAll("rect")
.data(mentorData)
.enter()
.append("rect")
.attr("x", (d, i) => 20 + i * 80)
.attr("y", 40)
.attr("width", 70)
.attr("height", 50)
.attr("rx", 5)
.attr("fill", "#d8b4fe")
.attr("stroke", "#5b21b6");
lspSvg.selectAll("text")
.data(mentorData)
.enter()
.append("text")
.text(d => d.type.split(" ")[0])
.attr("x", (d, i) => 55 + i * 80)
.attr("y", 70)
.attr("text-anchor", "middle")
.attr("fill", "#5b21b6")
.attr("font-size", "10px");
// Interface Segregation Chart
const ispSvg = d3.select("#isp-chart")
.append("svg")
.attr("width", "100%")
.attr("height", "100%");
ispSvg.append("circle")
.attr("cx", 90)
.attr("cy", 70)
.attr("r", 50)
.attr("fill", "none")
.attr("stroke", "#fcd34d")
.attr("stroke-width", 2)
.attr("stroke-dasharray", "5,5");
ispSvg.selectAll("path")
.data([30, 150, 270])
.enter()
.append("path")
.attr("d", d => {
const x = 90 + 40 * Math.cos(d * Math.PI / 180);
const y = 70 + 40 * Math.sin(d * Math.PI / 180);
return `M90,70 L${x},${y}`;
})
.attr("stroke", "#f59e0b")
.attr("stroke-width", 2);
ispSvg.selectAll("circle.small")
.data([30, 150, 270])
.enter()
.append("circle")
.attr("cx", d => 90 + 50 * Math.cos(d * Math.PI / 180))
.attr("cy", d => 70 + 50 * Math.sin(d * Math.PI / 180))
.attr("r", 15)
.attr("fill", "#fde68a")
.attr("stroke", "#b45309");
// Dependency Inversion Chart
const dipSvg = d3.select("#dip-chart")
.append("svg")
.attr("width", "100%")
.attr("height", "100%");
dipSvg.append("rect")
.attr("x", 60)
.attr("y", 30)
.attr("width", 80)
.attr("height", 40)
.attr("rx", 5)
.attr("fill", "#fecaca")
.attr("stroke", "#991b1b");
dipSvg.append("text")
.text("Themes")
.attr("x", 100)
.attr("y", 55)
.attr("text-anchor", "middle")
.attr("fill", "#991b1b");
dipSvg.append("rect")
.attr("x", 30)
.attr("y", 90)
.attr("width", 60)
.attr("height", 30)
.attr("rx", 5)
.attr("fill", "#fca5a5")
.attr("stroke", "#991b1b");
dipSvg.append("rect")
.attr("x", 110)
.attr("y", 90)
.attr("width", 60)
.attr("height", 30)
.attr("rx", 5)
.attr("fill", "#fca5a5")
.attr("stroke", "#991b1b");
dipSvg.append("text")
.text("Plot A")
.attr("x", 60)
.attr("y", 110)
.attr("text-anchor", "middle")
.attr("fill", "#991b1b")
.attr("font-size", "10px");
dipSvg.append("text")
.text("Plot B")
.attr("x", 140)
.attr("y", 110)
.attr("text-anchor", "middle")
.attr("fill", "#991b1b")
.attr("font-size", "10px");
dipSvg.append("path")
.attr("d", "M100,70 L80,90")
.attr("stroke", "#991b1b")
.attr("stroke-width", 1)
.attr("marker-end", "url(#arrow)");
dipSvg.append("path")
.attr("d", "M100,70 L120,90")
.attr("stroke", "#991b1b")
.attr("stroke-width", 1)
.attr("marker-end", "url(#arrow)");
// DRY Principle Chart
const drySvg = d3.select("#dry-chart")
.append("svg")
.attr("width", "100%")
.attr("height", "100%");
drySvg.selectAll("rect")
.data([1, 2, 3])
.enter()
.append("rect")
.attr("x", d => 30 + d * 40)
.attr("y", 40)
.attr("width", 30)
.attr("height", 60)
.attr("rx", 3)
.attr("fill", "#bfdbfe")
.attr("stroke", "#1e40af");
drySvg.append("rect")
.attr("x", 40)
.attr("y", 120)
.attr("width", 120)
.attr("height", 30)
.attr("rx", 3)
.attr("fill", "#93c5fd")
.attr("stroke", "#1e40af");
drySvg.append("text")
.text("Shared")
.attr("x", 100)
.attr("y", 140)
.attr("text-anchor", "middle")
.attr("fill", "#1e40af");
// Workflow Chart
const workflowSvg = d3.select("#workflow-chart")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.classed("flow-chart", true);
const workflowData = [
{id: 1, x: 100, y: 50, width: 120, height: 50, text: "Concept Development"},
{id: 2, x: 100, y: 130, width: 120, height: 50, text: "Outline Structure"},
{id: 3, x: 100, y: 210, width: 120, height: 50, text: "Character Design"},
{id: 4, x: 300, y: 50, width: 120, height: 50, text: "First Draft"},
{id: 5, x: 300, y: 130, width: 120, height: 50, text: "Revision"},
{id: 6, x: 300, y: 210, width: 120, height: 50, text: "Editing"},
{id: 7, x: 500, y: 130, width: 120, height: 50, text: "Publishing"}
];
workflowSvg.selectAll("rect")
.data(workflowData)
.enter()
.append("rect")
.attr("x", d => d.x)
.attr("y", d => d.y)
.attr("width", d => d.width)
.attr("height", d => d.height)
.attr("rx", 5);
workflowSvg.selectAll("text")
.data(workflowData)
.enter()
.append("text")
.text(d => d.text)
.attr("x", d => d.x + d.width/2)
.attr("y", d => d.y + d.height/2 + 5)
.attr("text-anchor", "middle");
workflowSvg.append("path")
.attr("d", "M220,75 L300,75")
.attr("marker-end", "url(#arrow)");
workflowSvg.append("path")
.attr("d", "M220,155 L300,155")
.attr("marker-end", "url(#arrow)");
workflowSvg.append("path")
.attr("d", "M420,155 L500,155")
.attr("marker-end", "url(#arrow)");
workflowSvg.append("path")
.attr("d", "M160,100 L160,130")
.attr("marker-end", "url(#arrow)");
workflowSvg.append("path")
.attr("d", "M160,180 L160,210")
.attr("marker-end", "url(#arrow)");
workflowSvg.append("path")
.attr("d", "M360,100 L360,130")
.attr("marker-end", "url(#arrow)");
workflowSvg.append("path")
.attr("d", "M360,180 L360,210")
.attr("marker-end", "url(#arrow)");
// Character Dependency Graph
const characterData = {
nodes: [
{id: 1, name: "Protagonist", role: "Main character", group: 1},
{id: 2, name: "Antagonist", role: "Primary opposition", group: 2},
{id: 3, name: "Mentor", role: "Provides guidance", group: 3},
{id: 4, name: "Love Interest", role: "Romantic subplot", group: 4},
{id: 5, name: "Sidekick", role: "Comic relief/support", group: 5},
{id: 6, name: "Foil", role: "Contrast to protagonist", group: 6}
],
links: [
{source: 1, target: 2, value: "Conflict"},
{source: 1, target: 3, value: "Guidance"},
{source: 1, target: 4, value: "Romance"},
{source: 1, target: 5, value: "Support"},
{source: 1, target: 6, value: "Contrast"},
{source: 2, target: 3, value: "Opposes"},
{source: 4, target: 5, value: "Friendship"}
]
};
const charSvg = d3.select("#character-graph")
.append("svg")
.attr("width", "100%")
.attr("height", "100%");
const width = document.getElementById("character-graph").clientWidth;
const height = document.getElementById("character-graph").clientHeight;
const simulation = d3.forceSimulation(characterData.nodes)
.force("link", d3.forceLink(characterData.links).id(d => d.id).distance(100))
.force("charge", d3.forceManyBody().strength(-300))
.force("center", d3.forceCenter(width / 2, height / 2));
const link = charSvg.append("g")
.selectAll("line")
.data(characterData.links)
.enter()
.append("line")
.attr("stroke-width", 1.5)
.attr("stroke", "#9ca3af");
const node = charSvg.append("g")
.selectAll("circle")
.data(characterData.nodes)
.enter()
.append("g")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("circle")
.attr("r", 20)
.attr("fill", d => {
const colors = ["#3b82f6", "#ef4444", "#10b981", "#f59e0b", "#8b5cf6", "#ec4899"];
return colors[d.group - 1];
});
node.append("text")
.text(d => d.name.charAt(0))
.attr("dy", 5)
.attr("text-anchor", "middle")
.attr("fill", "white");
const tooltip = d3.select(".tooltip");
node.on("mouseover", function(event, d) {
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(`<strong>${d.name}</strong><br/>${d.role}`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 28) + "px");
})
.on("mouseout", function() {
tooltip.transition()
.duration(500)
.style("opacity", 0);
});
simulation.on("tick", () => {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
node
.attr("transform", d => `translate(${d.x},${d.y})`);
});
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
// Plot Structure Visualization
const plotData = [
{x: 0, y: 100, label: "Exposition"},
{x: 150, y: 50, label: "Inciting Incident"},
{x: 300, y: 200, label: "Rising Action"},
{x: 450, y: 50, label: "Climax"},
{x: 600, y: 150, label: "Falling Action"},
{x: 750, y: 100, label: "Resolution"}
];
const plotSvg = d3.select("#plot-structure")
.append("svg")
.attr("width", "100%")
.attr("height", "100%");
const plotWidth = document.getElementById("plot-structure").clientWidth;
const plotHeight = document.getElementById("plot-structure").clientHeight;
const xScale = d3.scaleLinear()
.domain([0, 750])
.range([50, plotWidth - 50]);
const yScale = d3.scaleLinear()
.domain([0, 200])
.range([plotHeight - 100, 50]);
const line = d3.line()
.x(d => xScale(d.x))
.y(d => yScale(d.y))
.curve(d3.curveBasis);
plotSvg.append("path")
.datum(plotData)
.attr("d", line)
.attr("fill", "none")
.attr("stroke", "#3b82f6")
.attr("stroke-width", 3);
plotSvg.selectAll("circle")
.data(plotData)
.enter()
.append("circle")
.attr("cx", d => xScale(d.x))
.attr("cy", d => yScale(d.y))
.attr("r", 6)
.attr("fill", "#1d4ed8");
plotSvg.selectAll("text")
.data(plotData)
.enter()
.append("text")
.text(d => d.label)
.attr("x", d => xScale(d.x))
.attr("y", d => yScale(d.y) - 15)
.attr("text-anchor", "middle")
.attr("fill", "#1e40af")
.attr("font-size", "12px");
// Add arrow markers
plotSvg.append("defs").append("marker")
.attr("id", "arrow")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 10)
.attr("refY", 0)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5")
.attr("fill", "#999");
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=arirajuns/writing1" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>