changed the code render for the side quests tab
This commit is contained in:
40
src/components/CodeBlock.tsx
Normal file
40
src/components/CodeBlock.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||||
|
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||||
|
import '../styles/components/codeBlock.css';
|
||||||
|
|
||||||
|
interface CodeBlockProps {
|
||||||
|
language?: string;
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CodeBlock: React.FC<CodeBlockProps> = ({ language, code }) => (
|
||||||
|
<div className="code-block-container">
|
||||||
|
<div className="code-block-header">
|
||||||
|
<div className="window-controls">
|
||||||
|
<div className="control-dot red"></div>
|
||||||
|
<div className="control-dot yellow"></div>
|
||||||
|
<div className="control-dot green"></div>
|
||||||
|
</div>
|
||||||
|
<span className="language-label">
|
||||||
|
{language || 'text'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SyntaxHighlighter
|
||||||
|
language={language || 'text'}
|
||||||
|
style={vscDarkPlus}
|
||||||
|
customStyle={{
|
||||||
|
margin: 0,
|
||||||
|
borderRadius: '0 0 12px 12px',
|
||||||
|
fontSize: '14px',
|
||||||
|
background: 'transparent',
|
||||||
|
padding: '20px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{code}
|
||||||
|
</SyntaxHighlighter>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default CodeBlock;
|
||||||
@@ -2,6 +2,11 @@ import { motion, Variants } from "framer-motion";
|
|||||||
import "../styles/components/cards.css";
|
import "../styles/components/cards.css";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
export type DetailSection =
|
||||||
|
| { type: 'text'; title?: string; content: string }
|
||||||
|
| { type: 'code'; title?: string; language?: string; code: string }
|
||||||
|
| { type: 'image'; src: string; alt?: string; caption?: string; title?: string };
|
||||||
|
|
||||||
export interface Project {
|
export interface Project {
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -13,12 +18,7 @@ export interface Project {
|
|||||||
repo?: string;
|
repo?: string;
|
||||||
};
|
};
|
||||||
hasDetails?: boolean;
|
hasDetails?: boolean;
|
||||||
detailContent?: {
|
sections?: DetailSection[];
|
||||||
longDescription: string;
|
|
||||||
challenges: string;
|
|
||||||
learnings: string;
|
|
||||||
codeSnippet?: string;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ProjectCardProps {
|
interface ProjectCardProps {
|
||||||
|
|||||||
36
src/components/RichTextRenderer.tsx
Normal file
36
src/components/RichTextRenderer.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import CodeBlock from './CodeBlock';
|
||||||
|
|
||||||
|
interface RichTextRendererProps {
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RichTextRenderer: React.FC<RichTextRendererProps> = ({ content }) => {
|
||||||
|
if (!content) return null;
|
||||||
|
|
||||||
|
const parts = content.split(/```/);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rich-text-content">
|
||||||
|
{parts.map((part, index) => {
|
||||||
|
if (index % 2 === 1) {
|
||||||
|
// Code block
|
||||||
|
const firstLineBreak = part.indexOf('\n');
|
||||||
|
const language = part.slice(0, firstLineBreak).trim();
|
||||||
|
const code = part.slice(firstLineBreak + 1).trim();
|
||||||
|
|
||||||
|
return <CodeBlock key={index} language={language} code={code} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular text (split by newlines for paragraph handling)
|
||||||
|
return (
|
||||||
|
<span key={index} style={{ whiteSpace: 'pre-wrap' }}>
|
||||||
|
{part}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RichTextRenderer;
|
||||||
@@ -28,10 +28,17 @@ export const SIDEQUESTS: Project[] = [
|
|||||||
repo: "https://github.com/Bayda77/sidequests", // Placeholder link
|
repo: "https://github.com/Bayda77/sidequests", // Placeholder link
|
||||||
},
|
},
|
||||||
hasDetails: true,
|
hasDetails: true,
|
||||||
detailContent: {
|
sections: [
|
||||||
longDescription: "This project started as a deep dive into the world of fragment shaders. I wanted to understand how to create organic-looking textures without relying on image assets. Using Perlin noise and fractional Brownian motion (fBm), I created a dynamic, shifting terrain that reacts to time and mouse input.",
|
{
|
||||||
codeSnippet: `\`\`\`glsl
|
type: 'text',
|
||||||
float noise(vec2 st) {
|
title: 'Overview',
|
||||||
|
content: "This project started as a deep dive into the world of fragment shaders. I wanted to understand how to create organic-looking textures without relying on image assets. Using Perlin noise and fractional Brownian motion (fBm), I created a dynamic, shifting terrain that reacts to time and mouse input."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'code',
|
||||||
|
language: 'glsl',
|
||||||
|
title: 'Noise Function',
|
||||||
|
code: `float noise(vec2 st) {
|
||||||
vec2 i = floor(st);
|
vec2 i = floor(st);
|
||||||
vec2 f = fract(st);
|
vec2 f = fract(st);
|
||||||
float a = random(i);
|
float a = random(i);
|
||||||
@@ -40,11 +47,19 @@ float noise(vec2 st) {
|
|||||||
float d = random(i + vec2(1.0, 1.0));
|
float d = random(i + vec2(1.0, 1.0));
|
||||||
vec2 u = f * f * (3.0 - 2.0 * f);
|
vec2 u = f * f * (3.0 - 2.0 * f);
|
||||||
return mix(a, b, u.x) + (c - a)* u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
|
return mix(a, b, u.x) + (c - a)* u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
|
||||||
}
|
}`
|
||||||
\`\`\``,
|
},
|
||||||
challenges: "One of the biggest challenges was optimizing the GLSL code to run smoothly on lower-end devices. Mathematical operations in the fragment shader can get expensive quickly.",
|
{
|
||||||
learnings: "I gained a much deeper understanding of the graphics pipeline, vector math, and how to think about visuals in terms of mathematical functions rather than pixels."
|
type: 'text',
|
||||||
|
title: 'Challenges',
|
||||||
|
content: "One of the biggest challenges was optimizing the GLSL code to run smoothly on lower-end devices. Mathematical operations in the fragment shader can get expensive quickly."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
title: 'Learnings',
|
||||||
|
content: "I gained a much deeper understanding of the graphics pipeline, vector math, and how to think about visuals in terms of mathematical functions rather than pixels."
|
||||||
}
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,84 +1,10 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
import { useParams, Link } from "react-router-dom";
|
import { useParams, Link } from "react-router-dom";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
||||||
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
|
||||||
import { SIDEQUESTS } from "./Projects";
|
import { SIDEQUESTS } from "./Projects";
|
||||||
import { useEffect } from "react";
|
import CodeBlock from "../components/CodeBlock";
|
||||||
|
import RichTextRenderer from "../components/RichTextRenderer";
|
||||||
const RichTextRenderer = ({ content }: { content: string }) => {
|
import "../styles/pages/sidequestDetail.css";
|
||||||
if (!content) return null;
|
|
||||||
|
|
||||||
const parts = content.split(/```/);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="rich-text-content">
|
|
||||||
{parts.map((part, index) => {
|
|
||||||
if (index % 2 === 1) {
|
|
||||||
// Code block
|
|
||||||
const firstLineBreak = part.indexOf('\n');
|
|
||||||
const language = part.slice(0, firstLineBreak).trim();
|
|
||||||
const code = part.slice(firstLineBreak + 1).trim();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={index} style={{
|
|
||||||
margin: '30px 0',
|
|
||||||
borderRadius: '12px',
|
|
||||||
overflow: 'hidden',
|
|
||||||
border: '1px solid rgba(255, 255, 255, 0.1)',
|
|
||||||
background: '#1e1e1e', // Match VS Code dark background roughly or stay transparent if needed
|
|
||||||
boxShadow: '0 10px 30px rgba(0,0,0,0.3)'
|
|
||||||
}}>
|
|
||||||
{/* Window Header */}
|
|
||||||
<div style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: '12px 16px',
|
|
||||||
background: 'rgba(255, 255, 255, 0.05)',
|
|
||||||
borderBottom: '1px solid rgba(255, 255, 255, 0.05)'
|
|
||||||
}}>
|
|
||||||
<div style={{ display: 'flex', gap: '8px', marginRight: 'auto' }}>
|
|
||||||
<div style={{ width: '12px', height: '12px', borderRadius: '50%', background: '#ff5f56' }}></div>
|
|
||||||
<div style={{ width: '12px', height: '12px', borderRadius: '50%', background: '#ffbd2e' }}></div>
|
|
||||||
<div style={{ width: '12px', height: '12px', borderRadius: '50%', background: '#27c93f' }}></div>
|
|
||||||
</div>
|
|
||||||
<span style={{
|
|
||||||
fontSize: '12px',
|
|
||||||
color: 'rgba(255, 255, 255, 0.5)',
|
|
||||||
fontFamily: 'monospace',
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
marginTop: '2px'
|
|
||||||
}}>
|
|
||||||
{language || 'text'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SyntaxHighlighter
|
|
||||||
language={language || 'text'}
|
|
||||||
style={vscDarkPlus}
|
|
||||||
customStyle={{
|
|
||||||
margin: 0,
|
|
||||||
borderRadius: '0 0 12px 12px', // Only round bottom
|
|
||||||
fontSize: '14px',
|
|
||||||
background: 'transparent', // Let container bg show or stick to theme
|
|
||||||
padding: '20px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{code}
|
|
||||||
</SyntaxHighlighter>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regular text (split by newlines for paragraph handling)
|
|
||||||
return (
|
|
||||||
<span key={index} style={{ whiteSpace: 'pre-wrap' }}>
|
|
||||||
{part}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function SidequestDetail() {
|
export default function SidequestDetail() {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
@@ -90,109 +16,91 @@ export default function SidequestDetail() {
|
|||||||
|
|
||||||
if (!project) {
|
if (!project) {
|
||||||
return (
|
return (
|
||||||
<div style={{ minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center", flexDirection: "column", color: "white" }}>
|
<div className="sidequest-error-container">
|
||||||
<h2>Sidequest not found</h2>
|
<h2>Sidequest not found</h2>
|
||||||
<Link to="/projects" style={{ color: "rgba(255,255,255,0.7)", marginTop: "20px" }}>Back to Projects</Link>
|
<Link to="/projects" className="sidequest-back-link-error">Back to Projects</Link>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mainContentBlock" style={{ minWidth: "66vw", display: "flex", justifyContent: "center", paddingTop: "40px" }}>
|
<div className="mainContentBlock sidequest-container">
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.6 }}
|
transition={{ duration: 0.6 }}
|
||||||
style={{ width: "100%", maxWidth: "800px", padding: "0 20px" }}
|
className="sidequest-content-wrapper"
|
||||||
>
|
>
|
||||||
<Link to="/projects" style={{ display: "inline-flex", alignItems: "center", color: "rgba(255,255,255,0.6)", textDecoration: "none", marginBottom: "30px" }}>
|
<Link to="/projects" className="sidequest-back-link">
|
||||||
← Back to Projects
|
← Back to Projects
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div style={{ marginBottom: "40px" }}>
|
<div className="sidequest-header">
|
||||||
<h1 style={{ fontSize: "2.5rem", marginBottom: "10px", color: "white" }}>{project.title}</h1>
|
<h1 className="sidequest-title">{project.title}</h1>
|
||||||
<div style={{ display: "flex", flexWrap: "wrap", gap: "10px", marginBottom: "20px" }}>
|
<div className="sidequest-tech-stack">
|
||||||
{project.techStack.map(tech => (
|
{project.techStack.map(tech => (
|
||||||
<span key={tech} style={{
|
<span key={tech} className="sidequest-tech-tag">
|
||||||
background: "rgba(255,255,255,0.1)",
|
|
||||||
padding: "4px 12px",
|
|
||||||
borderRadius: "15px",
|
|
||||||
fontSize: "0.85rem",
|
|
||||||
color: "rgba(255,255,255,0.9)"
|
|
||||||
}}>
|
|
||||||
{tech}
|
{tech}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{
|
<div className="sidequest-hero-image-container">
|
||||||
width: "100%",
|
|
||||||
height: "400px",
|
|
||||||
borderRadius: "16px",
|
|
||||||
overflow: "hidden",
|
|
||||||
marginBottom: "40px",
|
|
||||||
boxShadow: "0 10px 30px rgba(0,0,0,0.3)"
|
|
||||||
}}>
|
|
||||||
<img
|
<img
|
||||||
src={project.image}
|
src={project.image}
|
||||||
alt={project.title}
|
alt={project.title}
|
||||||
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
className="sidequest-hero-image"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{
|
<div className="sidequest-details-container">
|
||||||
background: "rgba(255, 255, 255, 0.05)",
|
{/* Dynamic Sections Rendering */}
|
||||||
backdropFilter: "blur(10px)",
|
{project.sections?.map((section, index) => {
|
||||||
borderRadius: "16px",
|
switch (section.type) {
|
||||||
padding: "40px",
|
case 'text':
|
||||||
border: "1px solid rgba(255, 255, 255, 0.1)"
|
return (
|
||||||
}}>
|
<section key={index} className="sidequest-section">
|
||||||
<section style={{ marginBottom: "30px" }}>
|
{section.title && <h2 className="sidequest-section-title">{section.title}</h2>}
|
||||||
<h2 style={{ color: "white", fontSize: "1.5rem", marginBottom: "15px" }}>Overview</h2>
|
<div className="sidequest-section-content">
|
||||||
<div style={{ color: "rgba(255,255,255,0.8)", lineHeight: "1.7" }}>
|
<RichTextRenderer content={section.content} />
|
||||||
<RichTextRenderer content={project.detailContent?.longDescription || project.description} />
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
);
|
||||||
{project.detailContent?.codeSnippet && (
|
case 'code':
|
||||||
<div style={{ marginBottom: "30px" }}>
|
return (
|
||||||
<RichTextRenderer content={project.detailContent.codeSnippet} />
|
<section key={index} className="sidequest-section">
|
||||||
</div>
|
{section.title && <h2 className="sidequest-section-title">{section.title}</h2>}
|
||||||
)}
|
<CodeBlock language={section.language} code={section.code} />
|
||||||
|
|
||||||
{project.detailContent?.challenges && (
|
|
||||||
<section style={{ marginBottom: "30px" }}>
|
|
||||||
<h2 style={{ color: "white", fontSize: "1.5rem", marginBottom: "15px" }}>Challenges</h2>
|
|
||||||
<div style={{ color: "rgba(255,255,255,0.8)", lineHeight: "1.7" }}>
|
|
||||||
<RichTextRenderer content={project.detailContent.challenges} />
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
)}
|
);
|
||||||
|
case 'image':
|
||||||
{project.detailContent?.learnings && (
|
return (
|
||||||
<section>
|
<section key={index} className="sidequest-section">
|
||||||
<h2 style={{ color: "white", fontSize: "1.5rem", marginBottom: "15px" }}>Learnings</h2>
|
{section.title && <h2 className="sidequest-section-title">{section.title}</h2>}
|
||||||
<div style={{ color: "rgba(255,255,255,0.8)", lineHeight: "1.7" }}>
|
<img src={section.src} alt={section.alt || ''} className="sidequest-section-image" />
|
||||||
<RichTextRenderer content={project.detailContent.learnings} />
|
{section.caption && <p className="sidequest-section-caption">{section.caption}</p>}
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
)}
|
);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
|
||||||
<div style={{ marginTop: "40px", paddingTop: "20px", borderTop: "1px solid rgba(255,255,255,0.1)", display: "flex", gap: "20px" }}>
|
<div className="sidequest-footer-links">
|
||||||
{project.links.demo && (
|
{project.links.demo && (
|
||||||
<a href={project.links.demo} target="_blank" rel="noopener noreferrer" style={{ color: "white", textDecoration: "underline" }}>
|
<a href={project.links.demo} target="_blank" rel="noopener noreferrer" className="sidequest-footer-link">
|
||||||
Live Demo ↗
|
Live Demo ↗
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
{project.links.repo && (
|
{project.links.repo && (
|
||||||
<a href={project.links.repo} target="_blank" rel="noopener noreferrer" style={{ color: "white", textDecoration: "underline" }}>
|
<a href={project.links.repo} target="_blank" rel="noopener noreferrer" className="sidequest-footer-link">
|
||||||
View Code ↗
|
View Code ↗
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ height: "100px" }}></div>
|
<div className="sidequest-footer-spacer"></div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
48
src/styles/components/codeBlock.css
Normal file
48
src/styles/components/codeBlock.css
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
.code-block-container {
|
||||||
|
margin: 30px 0;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
background: #1e1e1e;
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-dot {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-dot.red {
|
||||||
|
background: #ff5f56;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-dot.yellow {
|
||||||
|
background: #ffbd2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-dot.green {
|
||||||
|
background: #27c93f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
font-family: monospace;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
131
src/styles/pages/sidequestDetail.css
Normal file
131
src/styles/pages/sidequestDetail.css
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
.sidequest-error-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidequest-back-link-error {
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidequest-container {
|
||||||
|
min-width: 66vw;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidequest-content-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 800px;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidequest-back-link {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
text-decoration: none;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidequest-back-link:hover {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidequest-header {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidequest-title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidequest-tech-stack {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidequest-tech-tag {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidequest-hero-image-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 400px;
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidequest-hero-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidequest-details-container {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 40px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidequest-section {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidequest-section-title {
|
||||||
|
color: white;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidequest-section-content {
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidequest-section-image {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidequest-section-caption {
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-top: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidequest-footer-links {
|
||||||
|
margin-top: 40px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidequest-footer-link {
|
||||||
|
color: white;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidequest-footer-spacer {
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user