summaryrefslogtreecommitdiff
path: root/front/src/styles
diff options
context:
space:
mode:
authorpolwex <polwex@sortug.com>2025-09-11 01:48:14 +0700
committerpolwex <polwex@sortug.com>2025-09-11 01:48:14 +0700
commitb1d68ac307ed87d63e83820cbdf843fff0fd9f7f (patch)
treed6a684a70a80509e68ff667b842aa4e4c091906f /front/src/styles
init
Diffstat (limited to 'front/src/styles')
-rw-r--r--front/src/styles/ThemeProvider.tsx302
-rw-r--r--front/src/styles/ThemeSwitcher.css249
-rw-r--r--front/src/styles/ThemeSwitcher.tsx131
-rw-r--r--front/src/styles/styles.css438
-rw-r--r--front/src/styles/trill.css612
5 files changed, 1732 insertions, 0 deletions
diff --git a/front/src/styles/ThemeProvider.tsx b/front/src/styles/ThemeProvider.tsx
new file mode 100644
index 0000000..2cc0ca6
--- /dev/null
+++ b/front/src/styles/ThemeProvider.tsx
@@ -0,0 +1,302 @@
+import React, {
+ createContext,
+ useContext,
+ useEffect,
+ useState,
+ type ReactNode,
+} from "react";
+
+export type ThemeName =
+ | "light"
+ | "dark"
+ | "sepia"
+ | "noir"
+ | "ocean"
+ | "forest"
+ | "gruvbox";
+
+export interface ThemeColors {
+ primary: string;
+ primaryHover: string;
+ secondary: string;
+ background: string;
+ surface: string;
+ surfaceHover: string;
+ text: string;
+ textSecondary: string;
+ textMuted: string;
+ border: string;
+ borderLight: string;
+ success: string;
+ warning: string;
+ error: string;
+ info: string;
+ link: string;
+ linkHover: string;
+ shadow: string;
+ overlay: string;
+}
+
+export interface Theme {
+ name: ThemeName;
+ colors: ThemeColors;
+}
+
+const themes: Record<ThemeName, Theme> = {
+ light: {
+ name: "light",
+ colors: {
+ primary: "#543fd7",
+ primaryHover: "#4532b8",
+ secondary: "#f39c12",
+ background: "#ffffff",
+ surface: "#f8f9fa",
+ surfaceHover: "#e9ecef",
+ text: "#212529",
+ textSecondary: "#495057",
+ textMuted: "#6c757d",
+ border: "#dee2e6",
+ borderLight: "#e9ecef",
+ success: "#28a745",
+ warning: "#ffc107",
+ error: "#dc3545",
+ info: "#17a2b8",
+ link: "#543fd7",
+ linkHover: "#4532b8",
+ shadow: "rgba(0, 0, 0, 0.1)",
+ overlay: "rgba(0, 0, 0, 0.5)",
+ },
+ },
+ dark: {
+ name: "dark",
+ colors: {
+ primary: "#7c6ef7",
+ primaryHover: "#9085f9",
+ secondary: "#f39c12",
+ background: "#0d1117",
+ surface: "#161b22",
+ surfaceHover: "#21262d",
+ text: "#c9d1d9",
+ textSecondary: "#8b949e",
+ textMuted: "#6e7681",
+ border: "#30363d",
+ borderLight: "#21262d",
+ success: "#3fb950",
+ warning: "#d29922",
+ error: "#f85149",
+ info: "#58a6ff",
+ link: "#58a6ff",
+ linkHover: "#79b8ff",
+ shadow: "rgba(0, 0, 0, 0.3)",
+ overlay: "rgba(0, 0, 0, 0.7)",
+ },
+ },
+ sepia: {
+ name: "sepia",
+ colors: {
+ primary: "#8b4513",
+ primaryHover: "#6b3410",
+ secondary: "#d2691e",
+ background: "#f4e8d0",
+ surface: "#ede0c8",
+ surfaceHover: "#e6d9c0",
+ text: "#3e2723",
+ textSecondary: "#5d4037",
+ textMuted: "#6d4c41",
+ border: "#d7ccc8",
+ borderLight: "#e0d5d0",
+ success: "#689f38",
+ warning: "#ff9800",
+ error: "#d32f2f",
+ info: "#0288d1",
+ link: "#8b4513",
+ linkHover: "#6b3410",
+ shadow: "rgba(62, 39, 35, 0.1)",
+ overlay: "rgba(62, 39, 35, 0.5)",
+ },
+ },
+ noir: {
+ name: "noir",
+ colors: {
+ primary: "#ffffff",
+ primaryHover: "#e0e0e0",
+ secondary: "#808080",
+ background: "#000000",
+ surface: "#0a0a0a",
+ surfaceHover: "#1a1a1a",
+ text: "#ffffff",
+ textSecondary: "#b0b0b0",
+ textMuted: "#808080",
+ border: "#333333",
+ borderLight: "#1a1a1a",
+ success: "#4caf50",
+ warning: "#ff9800",
+ error: "#f44336",
+ info: "#2196f3",
+ link: "#b0b0b0",
+ linkHover: "#ffffff",
+ shadow: "rgba(255, 255, 255, 0.1)",
+ overlay: "rgba(0, 0, 0, 0.9)",
+ },
+ },
+ ocean: {
+ name: "ocean",
+ colors: {
+ primary: "#006994",
+ primaryHover: "#005577",
+ secondary: "#00acc1",
+ background: "#e1f5fe",
+ surface: "#b3e5fc",
+ surfaceHover: "#81d4fa",
+ text: "#01579b",
+ textSecondary: "#0277bd",
+ textMuted: "#4fc3f7",
+ border: "#81d4fa",
+ borderLight: "#b3e5fc",
+ success: "#00c853",
+ warning: "#ffab00",
+ error: "#d50000",
+ info: "#00b0ff",
+ link: "#0277bd",
+ linkHover: "#01579b",
+ shadow: "rgba(1, 87, 155, 0.1)",
+ overlay: "rgba(1, 87, 155, 0.5)",
+ },
+ },
+ forest: {
+ name: "forest",
+ colors: {
+ primary: "#2e7d32",
+ primaryHover: "#1b5e20",
+ secondary: "#689f38",
+ background: "#f1f8e9",
+ surface: "#dcedc8",
+ surfaceHover: "#c5e1a5",
+ text: "#1b5e20",
+ textSecondary: "#33691e",
+ textMuted: "#558b2f",
+ border: "#aed581",
+ borderLight: "#c5e1a5",
+ success: "#4caf50",
+ warning: "#ff9800",
+ error: "#f44336",
+ info: "#03a9f4",
+ link: "#388e3c",
+ linkHover: "#2e7d32",
+ shadow: "rgba(27, 94, 32, 0.1)",
+ overlay: "rgba(27, 94, 32, 0.5)",
+ },
+ },
+ gruvbox: {
+ name: "gruvbox",
+ colors: {
+ primary: "#fe8019",
+ primaryHover: "#d65d0e",
+ secondary: "#fabd2f",
+ background: "#282828",
+ surface: "#3c3836",
+ surfaceHover: "#504945",
+ text: "#ebdbb2",
+ textSecondary: "#d5c4a1",
+ textMuted: "#bdae93",
+ border: "#665c54",
+ borderLight: "#504945",
+ success: "#b8bb26",
+ warning: "#fabd2f",
+ error: "#fb4934",
+ info: "#83a598",
+ link: "#8ec07c",
+ linkHover: "#b8bb26",
+ shadow: "rgba(0, 0, 0, 0.3)",
+ overlay: "rgba(40, 40, 40, 0.8)",
+ },
+ },
+};
+
+interface ThemeContextType {
+ theme: Theme;
+ themeName: ThemeName;
+ setTheme: (name: ThemeName) => void;
+ availableThemes: ThemeName[];
+}
+
+const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
+
+interface ThemeProviderProps {
+ children: ReactNode;
+ defaultTheme?: ThemeName;
+}
+
+export const ThemeProvider: React.FC<ThemeProviderProps> = ({
+ children,
+ defaultTheme = "light",
+}) => {
+ const [themeName, setThemeName] = useState<ThemeName>(() => {
+ const savedTheme = localStorage.getItem("theme") as ThemeName;
+ if (savedTheme && themes[savedTheme]) {
+ return savedTheme;
+ }
+
+ if (
+ window.matchMedia &&
+ window.matchMedia("(prefers-color-scheme: dark)").matches
+ ) {
+ return "dark";
+ }
+
+ return defaultTheme;
+ });
+
+ const theme = themes[themeName];
+
+ useEffect(() => {
+ const root = document.documentElement;
+
+ root.setAttribute("data-theme", themeName);
+
+ Object.entries(theme.colors).forEach(([key, value]) => {
+ const cssVarName = `--color-${key.replace(/([A-Z])/g, "-$1").toLowerCase()}`;
+ root.style.setProperty(cssVarName, value);
+ });
+
+ localStorage.setItem("theme", themeName);
+ }, [themeName, theme]);
+
+ useEffect(() => {
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
+ const handleChange = (e: MediaQueryListEvent) => {
+ const savedTheme = localStorage.getItem("theme");
+ if (!savedTheme) {
+ setThemeName(e.matches ? "dark" : "light");
+ }
+ };
+
+ mediaQuery.addEventListener("change", handleChange);
+ return () => mediaQuery.removeEventListener("change", handleChange);
+ }, []);
+
+ const setTheme = (name: ThemeName) => {
+ if (themes[name]) {
+ setThemeName(name);
+ }
+ };
+
+ const value: ThemeContextType = {
+ theme,
+ themeName,
+ setTheme,
+ availableThemes: Object.keys(themes) as ThemeName[],
+ };
+
+ return (
+ <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
+ );
+};
+
+export const useTheme = (): ThemeContextType => {
+ const context = useContext(ThemeContext);
+ if (context === undefined) {
+ throw new Error("useTheme must be used within a ThemeProvider");
+ }
+ return context;
+};
diff --git a/front/src/styles/ThemeSwitcher.css b/front/src/styles/ThemeSwitcher.css
new file mode 100644
index 0000000..518a00d
--- /dev/null
+++ b/front/src/styles/ThemeSwitcher.css
@@ -0,0 +1,249 @@
+/* Theme Switcher Styles */
+
+/* Compact variant */
+.theme-switcher-compact {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+ padding: var(--spacing-sm) var(--spacing-md);
+ background-color: var(--color-surface);
+ border: 1px solid var(--color-border);
+ border-radius: var(--radius-full);
+ cursor: pointer;
+ transition: all var(--transition-fast);
+ font-size: var(--font-md);
+ color: var(--color-text);
+}
+
+.theme-switcher-compact:hover {
+ background-color: var(--color-surface-hover);
+ border-color: var(--color-primary);
+ transform: scale(1.05);
+}
+
+.theme-switcher-compact:active {
+ transform: scale(0.98);
+}
+
+.theme-switcher-compact .theme-icon {
+ font-size: 1.2em;
+ display: flex;
+ align-items: center;
+}
+
+.theme-switcher-compact .theme-label {
+ font-weight: var(--font-medium);
+}
+
+/* Buttons variant */
+.theme-switcher-buttons {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-md);
+}
+
+.theme-switcher-buttons .theme-label {
+ color: var(--color-text-secondary);
+ font-weight: var(--font-medium);
+}
+
+.theme-buttons-group {
+ display: flex;
+ gap: var(--spacing-xs);
+ background-color: var(--color-surface);
+ padding: var(--spacing-xs);
+ border-radius: var(--radius-lg);
+ border: 1px solid var(--color-border);
+}
+
+.theme-button {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-xs);
+ padding: var(--spacing-xs) var(--spacing-sm);
+ background-color: transparent;
+ border: 1px solid transparent;
+ border-radius: var(--radius-md);
+ cursor: pointer;
+ transition: all var(--transition-fast);
+ color: var(--color-text-secondary);
+ font-size: var(--font-sm);
+}
+
+.theme-button:hover {
+ background-color: var(--color-surface-hover);
+ color: var(--color-text);
+}
+
+.theme-button.active {
+ background-color: var(--color-primary);
+ color: white;
+ border-color: var(--color-primary);
+}
+
+.theme-button .theme-icon {
+ font-size: 1.1em;
+}
+
+.theme-button .theme-name {
+ display: none;
+}
+
+@media (min-width: 768px) {
+ .theme-button .theme-name {
+ display: inline;
+ }
+}
+
+/* Dropdown variant */
+.theme-switcher-dropdown {
+ position: relative;
+ display: inline-block;
+}
+
+.theme-dropdown-toggle {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+ padding: var(--spacing-sm) var(--spacing-md);
+ background-color: var(--color-surface);
+ border: 1px solid var(--color-border);
+ border-radius: var(--radius-md);
+ cursor: pointer;
+ transition: all var(--transition-fast);
+ color: var(--color-text);
+ font-size: var(--font-md);
+}
+
+.theme-dropdown-toggle:hover {
+ background-color: var(--color-surface-hover);
+ border-color: var(--color-primary);
+}
+
+.theme-dropdown-toggle .theme-icon {
+ font-size: 1.2em;
+}
+
+.theme-dropdown-toggle .theme-label {
+ font-weight: var(--font-medium);
+}
+
+.theme-dropdown-toggle .dropdown-arrow {
+ font-size: 0.7em;
+ margin-left: var(--spacing-xs);
+ transition: transform var(--transition-fast);
+ color: var(--color-text-muted);
+}
+
+.theme-dropdown-toggle[aria-expanded="true"] .dropdown-arrow {
+ transform: rotate(180deg);
+}
+
+.theme-dropdown-backdrop {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: var(--z-dropdown);
+ background-color: transparent;
+}
+
+.theme-dropdown-menu {
+ position: absolute;
+ top: calc(100% + var(--spacing-xs));
+ right: 0;
+ min-width: 180px;
+ background-color: var(--color-background);
+ border: 1px solid var(--color-border);
+ border-radius: var(--radius-lg);
+ box-shadow: 0 4px 12px var(--color-shadow);
+ z-index: calc(var(--z-dropdown) + 1);
+ padding: var(--spacing-xs);
+ animation: dropdownSlide 0.2s ease-out;
+}
+
+@keyframes dropdownSlide {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.theme-dropdown-item {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-sm);
+ width: 100%;
+ padding: var(--spacing-sm) var(--spacing-md);
+ background-color: transparent;
+ border: none;
+ border-radius: var(--radius-md);
+ cursor: pointer;
+ transition: all var(--transition-fast);
+ color: var(--color-text);
+ font-size: var(--font-md);
+ text-align: left;
+}
+
+.theme-dropdown-item:hover {
+ background-color: var(--color-surface);
+}
+
+.theme-dropdown-item.active {
+ background-color: var(--color-surface);
+ color: var(--color-primary);
+ font-weight: var(--font-medium);
+}
+
+.theme-dropdown-item .theme-icon {
+ font-size: 1.2em;
+ width: 1.5em;
+ text-align: center;
+}
+
+.theme-dropdown-item .theme-name {
+ flex: 1;
+}
+
+.theme-dropdown-item .checkmark {
+ color: var(--color-success);
+ font-weight: var(--font-bold);
+}
+
+/* Accessibility */
+.theme-switcher-compact:focus,
+.theme-button:focus,
+.theme-dropdown-toggle:focus,
+.theme-dropdown-item:focus {
+ outline: 2px solid var(--color-primary);
+ outline-offset: 2px;
+}
+
+/* Dark theme adjustments */
+[data-theme="dark"] .theme-dropdown-menu {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
+}
+
+/* Reduced motion */
+@media (prefers-reduced-motion: reduce) {
+ .theme-switcher-compact,
+ .theme-button,
+ .theme-dropdown-toggle,
+ .theme-dropdown-item,
+ .dropdown-arrow {
+ transition: none;
+ }
+
+ .theme-dropdown-menu {
+ animation: none;
+ }
+
+ .theme-switcher-compact:hover {
+ transform: none;
+ }
+} \ No newline at end of file
diff --git a/front/src/styles/ThemeSwitcher.tsx b/front/src/styles/ThemeSwitcher.tsx
new file mode 100644
index 0000000..425bed9
--- /dev/null
+++ b/front/src/styles/ThemeSwitcher.tsx
@@ -0,0 +1,131 @@
+import React, { useState } from "react";
+import { useTheme, type ThemeName } from "../styles/ThemeProvider";
+import "./ThemeSwitcher.css";
+
+interface ThemeSwitcherProps {
+ variant?: "dropdown" | "buttons" | "compact";
+ showLabel?: boolean;
+}
+
+const themeIcons: Record<ThemeName, string> = {
+ light: "☀️",
+ dark: "🌙",
+ sepia: "📜",
+ noir: "⚫",
+ ocean: "🌊",
+ forest: "🌲",
+ gruvbox: "🍂",
+};
+
+const themeLabels: Record<ThemeName, string> = {
+ light: "Light",
+ dark: "Dark",
+ sepia: "Sepia",
+ noir: "Noir",
+ ocean: "Ocean",
+ forest: "Forest",
+ gruvbox: "Gruvbox",
+};
+
+export const ThemeSwitcher: React.FC<ThemeSwitcherProps> = ({
+ variant = "dropdown",
+ showLabel = true,
+}) => {
+ const { themeName, setTheme, availableThemes } = useTheme();
+ const [isOpen, setIsOpen] = useState(false);
+
+ const handleThemeChange = (theme: ThemeName) => {
+ setTheme(theme);
+ setIsOpen(false);
+ };
+
+ const cycleTheme = () => {
+ const currentIndex = availableThemes.indexOf(themeName);
+ const nextIndex = (currentIndex + 1) % availableThemes.length;
+ setTheme(availableThemes[nextIndex]);
+ };
+
+ if (variant === "compact") {
+ return (
+ <button
+ className="theme-switcher-compact"
+ onClick={cycleTheme}
+ title={`Current theme: ${themeLabels[themeName]}. Click to switch.`}
+ aria-label="Switch theme"
+ >
+ <span className="theme-icon">{themeIcons[themeName]}</span>
+ {showLabel && (
+ <span className="theme-label">{themeLabels[themeName]}</span>
+ )}
+ </button>
+ );
+ }
+
+ if (variant === "buttons") {
+ return (
+ <div className="theme-switcher-buttons">
+ {showLabel && <span className="theme-label">Theme:</span>}
+ <div className="theme-buttons-group">
+ {availableThemes.map((theme) => (
+ <button
+ key={theme}
+ className={`theme-button ${themeName === theme ? "active" : ""}`}
+ onClick={() => handleThemeChange(theme)}
+ title={themeLabels[theme]}
+ aria-label={`Switch to ${themeLabels[theme]} theme`}
+ aria-pressed={themeName === theme}
+ >
+ <span className="theme-icon">{themeIcons[theme]}</span>
+ {showLabel && (
+ <span className="theme-name">{themeLabels[theme]}</span>
+ )}
+ </button>
+ ))}
+ </div>
+ </div>
+ );
+ }
+
+ // Default dropdown variant
+ return (
+ <div className="theme-switcher-dropdown">
+ <button
+ className="theme-dropdown-toggle"
+ onClick={() => setIsOpen(!isOpen)}
+ aria-haspopup="true"
+ aria-expanded={isOpen}
+ >
+ <span className="theme-icon">{themeIcons[themeName]}</span>
+ {showLabel && (
+ <span className="theme-label">{themeLabels[themeName]}</span>
+ )}
+ <span className="dropdown-arrow">▼</span>
+ </button>
+
+ {isOpen && (
+ <>
+ <div
+ className="theme-dropdown-backdrop"
+ onClick={() => setIsOpen(false)}
+ aria-hidden="true"
+ />
+ <div className="theme-dropdown-menu" role="menu">
+ {availableThemes.map((theme) => (
+ <button
+ key={theme}
+ className={`theme-dropdown-item ${themeName === theme ? "active" : ""}`}
+ onClick={() => handleThemeChange(theme)}
+ role="menuitem"
+ aria-selected={themeName === theme}
+ >
+ <span className="theme-icon">{themeIcons[theme]}</span>
+ <span className="theme-name">{themeLabels[theme]}</span>
+ {themeName === theme && <span className="checkmark">✓</span>}
+ </button>
+ ))}
+ </div>
+ </>
+ )}
+ </div>
+ );
+};
diff --git a/front/src/styles/styles.css b/front/src/styles/styles.css
new file mode 100644
index 0000000..c2b05d6
--- /dev/null
+++ b/front/src/styles/styles.css
@@ -0,0 +1,438 @@
+/* assets */
+/* fonts */
+@font-face {
+ font-family: "Inter";
+ src: url(/fonts/Inter/Inter-VariableFont_opsz,wght.ttf);
+ font-weight: 100 900;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Inter";
+ src: url(/fonts/Inter/Inter-Italic-VariableFont_opsz,wght.ttf);
+ font-weight: 100 900;
+ font-style: italic;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Source Code Pro";
+ src: url(/fonts/Source_Code_Pro/SourceCodePro-VariableFont_wght.ttf);
+ font-weight: 100 900;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Source Code Pro";
+ src: url(/fonts/Source_Code_Pro/SourceCodePro-Italic-VariableFont_wght.ttf);
+ font-weight: 100 900;
+ font-style: italic;
+ font-display: swap;
+}
+
+/* tailwindy */
+
+.grow {
+ flex-grow: 1;
+}
+
+button {
+ cursor: pointer;
+}
+
+code {
+ font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
+ monospace;
+}
+
+t .red {
+ background-color: rgb(200, 0, 0, 0.9);
+}
+
+.tc,
+.ct {
+ text-align: center;
+}
+
+.cb {
+ margin: auto;
+}
+
+.xc {
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
+}
+
+.hidden {
+ display: none;
+}
+
+.x-center {
+ margin: auto;
+ text-align: center;
+ display: block;
+}
+
+.flex {
+ display: flex;
+}
+
+.f1 {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.flex-align {
+ display: flex;
+ gap: 1rem;
+ align-items: center;
+}
+
+.noscroll {
+ overflow: hidden;
+}
+
+.scroll-y {
+ overflow-y: scroll;
+}
+
+.cp {
+ cursor: pointer;
+}
+
+.m0 {
+ margin: 0;
+}
+
+.mb {
+ margin: 0 0 1rem 0;
+}
+
+.mt {
+ margin-top: 1rem;
+}
+
+.mr {
+ margin-right: 0.5rem;
+}
+
+.s-50 {
+ width: 50px;
+}
+
+.s-100 {
+ width: 100px;
+}
+
+.border {
+ border: 1px solid var(--text-color);
+}
+
+/* styles */
+
+/* common */
+html {
+ box-sizing: border-box;
+ color: var(--text-color);
+ background-color: var(--background-color);
+}
+
+html,
+body,
+#root,
+#mobile-ui {
+ height: 100%;
+ width: 100vw;
+ overflow: hidden;
+ /* no scrolling!!!*/
+}
+
+*,
+*:before,
+*:after {
+ box-sizing: inherit;
+}
+
+body {
+ margin: 0;
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ background-color: var(--color-background);
+ color: var(--color-text);
+ line-height: 1.6;
+ transition: background-color var(--transition-normal), color var(--transition-normal);
+}
+
+/* Typography */
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ margin-bottom: var(--spacing-md);
+ font-weight: var(--font-semibold);
+ line-height: 1.2;
+ color: var(--color-text);
+}
+
+#root {
+ margin: 1rem 2rem;
+ height: 100%;
+ overflow-y: auto;
+ font-family: "Inter";
+
+
+ display: flex;
+
+ & #left-menu {
+ margin-right: 1rem;
+
+ #logo {
+ display: flex;
+ gap: 0.3rem;
+
+ & img {
+ width: 48px;
+ height: 48px;
+ }
+ }
+
+ & .opt {
+ cursor: pointer;
+ display: flex;
+ gap: 1rem;
+ margin: 1rem 0;
+
+ & img {
+ width: 24px;
+ height: 24px;
+ }
+ }
+ }
+
+ & main {
+ width: 726px;
+ margin: auto;
+ height: 100vh;
+
+ & #top-tabs {
+ display: flex;
+ gap: 2rem;
+ justify-content: center;
+
+ & div {
+ cursor: pointer;
+ }
+
+ & .active {
+ font-weight: 700;
+ border-bottom: 3px solid var(--color-text);
+ }
+ }
+
+ & #feed-proper {
+ margin-top: 1rem;
+ border: 1px solid grey;
+ border-radius: 0.75rem;
+
+ & #composer {
+ padding: 10px;
+ display: flex;
+ gap: 0.5rem;
+
+ & .sigil {
+ width: 48px;
+ height: 48px;
+
+ & img {
+ width: inherit;
+ }
+ }
+
+ & input {
+ background-color: transparent;
+ color: var(--color-text);
+ flex-grow: 1;
+ border: none;
+ outline: none;
+ }
+ }
+ }
+
+ & .trill-post,
+ & .twatter-post {
+ border-top: 1px solid grey;
+
+ & .left {
+ margin-right: 10px;
+ width: unset;
+
+ & .sigil {
+ width: 48px;
+ height: 48px;
+ }
+ }
+
+ & header {
+ align-items: center;
+ justify-content: left;
+
+ & .author {
+ flex: unset;
+ gap: 0;
+
+ & .name {
+ display: flex;
+ align-items: center;
+
+ & .p {
+ font-family: "Source Code Pro";
+ }
+ }
+ }
+
+ & .date {
+ color: grey;
+ }
+
+ }
+
+ & footer {
+ justify-content: left;
+ margin: unset;
+
+ & .icon {
+ margin: 0;
+ align-items: center;
+ gap: 0.2rem;
+ width: 64px;
+
+ & img {
+ height: 18px;
+ }
+
+ & .react-img {
+ height: 24px;
+ }
+
+ & .react-icon {
+ font-size: 20px;
+ }
+
+ & span {
+ margin-right: unset;
+ text-align: left;
+ font-size: 14px;
+ line-height: 1rem;
+ color: grey;
+ width: unset;
+ }
+ }
+
+ & .menu-icon {
+ margin-left: auto;
+ }
+ }
+ }
+
+
+ & .user-contact {
+ & .contact-cover {
+ margin-bottom: -40px;
+
+ & img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+ }
+
+ & .contact-name {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ }
+
+ & .contact-username {
+ margin-top: 1rem;
+ font-family: "Source Code Pro";
+ font-weight: 400;
+ }
+
+ & button {
+ width: unset;
+ margin: unset;
+ height: unset;
+ }
+ }
+ }
+
+ & button {
+ font-size: 0.9rem;
+ font-weight: 700;
+ line-height: 1rem;
+ border: none;
+ border-radius: 2rem;
+ padding: 0.5rem 2rem;
+ }
+
+ & .sigil,
+ & .sigil svg {
+ border-radius: 0.5rem;
+ }
+}
+
+#big-button {
+ position: absolute;
+ right: 2rem;
+ bottom: 2rem;
+ font-size: 1.5rem;
+ font-weight: bold;
+ cursor: pointer;
+ text-align: center;
+ line-height: 3rem;
+ width: 3rem;
+ height: 3rem;
+ border-radius: 50%;
+ z-index: 100;
+}
+
+/* modal */
+#modal-background {
+ height: 100vh;
+ width: 100vw;
+ background-color: rgb(0, 0, 0, 0.9);
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 100;
+}
+
+#modal {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ width: 80%;
+ z-index: 101;
+ transform: translate(-50%, -50%);
+ background-color: var(--background-color);
+ padding: 1rem;
+ max-height: 80%;
+}
+
+.modal-buttons {
+ display: flex;
+ justify-content: space-around;
+}
+
+::-webkit-scrollbar {
+ display: none;
+} \ No newline at end of file
diff --git a/front/src/styles/trill.css b/front/src/styles/trill.css
new file mode 100644
index 0000000..5687c7a
--- /dev/null
+++ b/front/src/styles/trill.css
@@ -0,0 +1,612 @@
+#not-found {
+ margin: auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+#not-found button {
+ height: 1.5rem;
+ margin: auto;
+}
+
+#timeline {
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+.timeline-post {
+ width: 100%;
+ border-top: 1px solid var(--text-color);
+}
+
+.trill-post {
+ display: flex;
+ padding: 0.5rem;
+ /* min-height: 150px; */
+}
+
+.trill-post .author {
+ flex: 1 0 auto;
+}
+
+.trill-reply-thread {
+ border-top: 1px solid var(--text-color);
+}
+
+.trill-post:first-child {
+ border-top: none;
+}
+
+.trill-post:last-child {
+ border-bottom: 1px solid var(--text-color);
+}
+
+.trill-post .left {
+ width: 8%;
+ margin-right: 1rem;
+}
+
+.trill-post .sigil {
+ height: 42px;
+ width: 42px;
+}
+
+.trill-post .right {
+ width: 90%;
+}
+
+.trill-post header {
+ display: flex;
+ justify-content: space-between;
+}
+
+.trill-post header p {
+ margin: 0 0.3rem;
+}
+
+.trill-post .nick {
+ font-weight: 700;
+}
+
+.trill-post header .p {
+ font-family: "Courier New", Courier, monospace;
+ font-weight: 100;
+ font-size: 1rem;
+}
+
+.trill-post .p-only {
+ margin: 0.7rem 0.3rem;
+ font-weight: 700;
+}
+
+.trill-post .p {
+ /* margin-top: -5px; */
+}
+
+.trill-post a {
+ text-decoration: 0;
+ color: var(--text-color);
+}
+
+.trill-post blockquote {
+ border-left: 2px solid grey;
+ margin-left: 0;
+ padding-left: 0.5rem;
+ opacity: 70%;
+}
+
+.trill-post .body {
+ margin: 1rem;
+ margin-left: 0;
+}
+
+.trill-post-body p span {
+ /* margin: 0 3px; */
+}
+
+.trill-post pre {
+ font-family: "Courier New", Courier, monospace;
+ background-color: rgb(200, 200, 200, 0.5);
+ padding: 0.2rem;
+ max-width: 90%;
+ border: 1px solid var(--text-color);
+ overflow: scroll;
+}
+
+.trill-post .quote-in-post .body {
+ margin: 0;
+}
+
+.quote-in-post svg {
+ margin-right: 0.5rem;
+}
+
+.trill-post .body-text {
+ /* font-family: Arial, Helvetica, sans-serif; */
+ margin: 0.3rem 0 1rem 0;
+ word-break: break-word;
+}
+
+.trill-post .trill-post-paragraph {
+ margin-block-start: 1em;
+ margin-block-end: 1em;
+}
+
+.trill-post .body-text a {
+ text-decoration: underline;
+}
+
+.trill-post .token {
+ margin: 0 0.5rem;
+}
+
+.trill-post .date {
+ float: right;
+}
+
+.trill-post .nav {
+ display: flex;
+}
+
+.trill-post .chevron {
+ width: 1.5rem;
+ height: 1.5rem;
+}
+
+.body-media {
+ width: 100%;
+ max-height: 520px;
+ text-align: center;
+ /* images being inline */
+}
+
+.body-media img {
+ margin: 1px 3px;
+}
+
+.body-img-1-of-1 {
+ max-width: 100%;
+ max-height: inherit;
+ margin: auto !important;
+}
+
+.body-img-1-of-2 {
+ max-width: 48.5%;
+ max-height: inherit;
+}
+
+.body-img-1-of-3 {
+ max-width: 48.5%;
+}
+
+.body-img-1-of-4 {
+ max-width: 48.5%;
+}
+
+.body-img-1-of-5 {
+ max-width: 31%;
+}
+
+.body-img-1-of-6 {
+ max-width: 31%;
+}
+
+.body-img-1-of-7 {
+ max-width: 31%;
+}
+
+.body-img-1-of-8 {
+ max-width: 31%;
+}
+
+.body-img-1-of-9 {
+ max-width: 31%;
+}
+
+/* quotes */
+
+.quote-in-post {
+ margin-top: 1rem;
+ padding: 0.5rem;
+ border: 1px solid grey;
+ border-radius: 0.5rem;
+ cursor: pointer;
+}
+
+.quote-in-post header {
+ display: flex;
+}
+
+.mention {
+ font-family: "Courier New", Courier, monospace;
+ font-weight: 700;
+}
+
+.mention:hover {
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+.bad-quote {
+ border: 1px solid var(--text-color);
+ padding: 7px;
+ border-radius: 0.5rem;
+}
+
+/* post-cards */
+.trill-post-card {
+ position: relative;
+ border-radius: 0.3rem;
+ /* margin: 1rem 0 0 -8%; */
+ margin: 0.5rem 0;
+}
+
+.trill-post-card-logo {
+ position: absolute;
+ width: 25px;
+ height: 25px;
+ top: -17px;
+ left: -17px;
+}
+
+#post-menu {
+ position: absolute;
+ top: 0;
+ right: 50px;
+ z-index: 99;
+}
+
+.deleted-post {
+ text-align: center;
+ border: 1px solid var(--text-color);
+ padding: 0.4rem;
+}
+
+#post-menu p {
+ background-color: var(--background-color);
+ margin: 0;
+ padding: 0.5rem;
+ cursor: pointer;
+ border: 1px solid var(--text-color);
+ height: 40px;
+}
+
+#post-menu p:hover {
+ /* background-color: var(--highlighted-grey); */
+}
+
+/* threads */
+.trill-reply-thread {
+ margin-top: 1rem;
+}
+
+#replies>.trill-post:first-child {
+ border-top: 1px solid black;
+}
+
+/* footer */
+
+.footer-wrapper {
+ position: relative;
+ /* transform: rotate(0deg); */
+ /* the dummy transform enforces position fixed inheritance */
+}
+
+.post-footer footer {
+ display: flex;
+ margin-left: -20px;
+ height: 24px;
+ justify-content: space-between;
+}
+
+footer .icon {
+ cursor: pointer;
+ margin: 0 0.2rem;
+ display: flex;
+ /* min-width: 64px; */
+}
+
+footer #menu-icon {
+ width: 32px !important;
+ /* margin-left: 20px; */
+}
+
+.post-footer footer .icon img {
+ display: block;
+ width: 24px;
+ height: 24px;
+}
+
+footer .icon span {
+ display: block;
+ width: 30px;
+ text-align: right;
+ padding-top: 0.2rem;
+ margin-right: 0.4rem;
+}
+
+footer .icon span:hover {
+ text-decoration: underline;
+}
+
+.react-icon {
+ font-size: 26px;
+ margin: -10px 0 0 0 !important;
+ padding: 0;
+ padding-top: 0 !important;
+}
+
+#react-list {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+#react-list img {
+ margin: 3px;
+ width: 50px;
+ height: 50px;
+ cursor: pointer;
+ border: 1px solid transparent;
+}
+
+#react-list span {
+ width: 50px;
+ height: 50px;
+ font-size: 38px;
+ margin: 3px;
+ cursor: pointer;
+ border: 1px solid transparent;
+}
+
+#react-list span:hover,
+#react-list img:hover {
+ border: 1px solid var(--text-color);
+}
+
+#menu-background {
+ position: fixed;
+ top: 0;
+ left: 0;
+ opacity: 0;
+ height: 100vh;
+ width: 100vw;
+}
+
+/* contact */
+
+.contact-cover {
+ height: 150px;
+ max-width: 100vw;
+ margin-bottom: -50px;
+}
+
+#contact-proper {
+ padding: 1rem;
+}
+
+#contact-proper .row {
+ display: flex;
+}
+
+.contact-avatar {
+ width: 6rem;
+ height: 6rem;
+}
+
+.contact-name {
+ margin-top: 1rem;
+ margin-bottom: 0.5rem;
+ margin-left: 0.3rem;
+ font-weight: 700;
+ font-size: 1.1rem;
+}
+
+.contact-username {
+ margin-top: -10px;
+}
+
+#contact-proper .buttons {
+ margin-top: 2rem;
+ margin-left: auto;
+}
+
+#contact-proper .buttons button {
+ width: 5rem;
+ margin-bottom: 5px;
+ height: 1.5rem;
+}
+
+#contact-proper .p {
+ font-family: "Courier New", Courier, monospace;
+}
+
+#contact-proper .p-only {
+ margin-top: 1rem;
+}
+
+.bio-row {
+ display: flex;
+ align-items: center;
+}
+
+.stats-row {
+ display: flex;
+ justify-content: center;
+}
+
+.stats-icon {
+ margin: 0 2px;
+}
+
+.stats-row p {
+ text-align: center;
+ font-size: 1.3rem;
+ margin: -5px 0 0 0;
+}
+
+.stats-row img {
+ width: 32px;
+}
+
+.locked-notice,
+.suspended-notice {
+ text-align: center;
+}
+
+.cover-placeholder {
+ height: 150px;
+ background-color: rgb(125, 125, 125, 0.5);
+}
+
+#stats-modal .trill-post {
+ border-bottom: 1px solid var(--text-color) !important;
+}
+
+#stats-modal {
+ height: 80vh;
+}
+
+#stats-modal #engagement {
+ min-height: 40%;
+ max-height: 40%;
+ overflow-y: scroll;
+}
+
+#stats-modal .trill-post {
+ max-height: 50%;
+ overflow-y: scroll;
+}
+
+.btw {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+#stats-modal .react-stat img {
+ width: 32px;
+ height: 32px;
+}
+
+#stats-modal .react-stat react-icon {
+ width: 32px;
+ height: 32px;
+}
+
+#stats-modal #engagement .nickname {
+ font-size: 1rem;
+}
+
+#stats-modal #engagement .p {
+ font-size: 0.9rem;
+}
+
+#stats-modal .tab h4 {
+ font-weight: 100;
+}
+
+#stats-modal .tab.active-tab h4 {
+ font-weight: 700;
+}
+
+/* .not-found {
+ border: 1px solid var(--text-color);
+ border-radius: 1rem;
+ padding: 0.5rem;
+} */
+
+/* refs */
+.reference {}
+
+/* polls */
+.trill-poll {
+ /* border: 1px solid var(--text-color); */
+ border-radius: 1rem;
+ padding: 0.5rem;
+ position: relative;
+ background: linear-gradient(90deg,
+ rgba(255, 255, 168, 0.4) 0%,
+ /* Lighter yellow */
+ rgba(255, 233, 150, 0.5) 52%,
+ /* Mid-tone gold */
+ rgba(255, 209, 0, 0.4) 100%
+ /* Deeper gold */
+ );
+}
+
+.trill-poll .poll-option {
+ height: 2rem;
+ align-items: center;
+ text-align: center;
+ border: 1px solid var(--text-color);
+ border-radius: 0.7rem;
+ margin: 1rem;
+ position: relative;
+ outline: 3px solid transparent;
+}
+
+.trill-poll .my-vote:hover {
+ /* cursor:not-allowed */
+}
+
+.trill-poll .poll-option:hover {
+ opacity: 50%;
+ outline-color: var(--text-color);
+}
+
+.trill-poll .poll-option p {
+ padding: 0 0.5rem;
+ margin: 0;
+ line-height: 2rem;
+}
+
+.trill-poll .poll-option-stats {
+ height: 2rem;
+ position: relative;
+}
+
+.trill-poll .poll-option-bar {
+ height: 100%;
+ position: absolute;
+ background-color: rgb(100, 100, 100, 0.3);
+ border-radius: 0.7rem;
+}
+
+.trill-poll .my-vote {
+ border: 3px solid var(--text-color);
+ border-right: 4px solid var(--text-color);
+}
+
+.trill-poll .bottom-row {
+ opacity: 60%;
+}
+
+.youtube-thumbnail {
+ width: 70%;
+ margin: 0.7rem auto;
+}
+
+.cursor-button {
+ width: 100%;
+ padding: 1rem;
+ border-top: 1px solid var(--text-color);
+}
+
+.cursor-button button {
+ display: block;
+ margin: auto;
+ padding: 0.5rem;
+}
+
+.rumor-quote img {
+ width: 50px;
+ margin-right: 1rem;
+}
+
+#trill-thread {
+ flex-grow: 1;
+ height: 100%;
+ overflow-y: auto;
+
+
+} \ No newline at end of file