""" Sage & Sand — Neumorphic Gradio Theme For AUTOMATIC1111 Stable Diffusion WebUI Palette: #CCD5AE sage green (accent, highlights) #E9EDC9 pale sage (secondary accent) #FEFAE0 ivory cream (light surfaces) #FAEDCD warm parchment (mid surfaces) #D4A373 caramel tan (page background — all neumorphic shadows derived here) Neumorphic shadow values (derived from #D4A373): Light highlight : #E8C49A (+20% brightness) Dark recess : #A87845 (-20% brightness) Usage in AUTOMATIC1111: 1. Place theme.py and app.py in your A1111 root folder. 2. In webui.py, locate the gr.Blocks(...) call and add: from theme import SageAndSandNeumorphic, NEUMORPHIC_CSS with gr.Blocks(theme=SageAndSandNeumorphic(), css=NEUMORPHIC_CSS) as demo: 3. Restart the WebUI. Publishing to Hugging Face Hub (standalone theme): from theme import SageAndSandNeumorphic theme = SageAndSandNeumorphic() theme.push_to_hub( repo_name="NeoSand", org_name="kbray", hf_token="hf_...", ) """ import gradio as gr from gradio.themes.base import Base from gradio.themes.utils import colors, fonts, sizes # ── Palette constants ──────────────────────────────────────────────────────── BG = "#D4A373" # page background / neumorphic base LIGHT_SHDW = "#E8C49A" # raised highlight shadow DARK_SHDW = "#A87845" # raised depth shadow PANEL = "#DAAA7A" # panel — slightly darker than BG for recessed feel INPUT_BG = "#D9A870" # inputs — slightly darker, appear pressed in ACCENT = "#CCD5AE" # sage green ACCENT2 = "#E9EDC9" # pale sage CREAM = "#FEFAE0" # text-on-dark surfaces / bright highlights PARCHMENT = "#FAEDCD" # lighter raised surfaces TEXT_DARK = "#3a3a2a" # body text on light TEXT_MID = "#5a4a30" # body text on tan TEXT_MUTED = "#7a6248" # muted / label text on tan # ── Neumorphic CSS (injected via gr.Blocks(css=NEUMORPHIC_CSS)) ────────────── # Gradio's token system does not expose every element's box-shadow, so we # supplement the theme tokens with targeted CSS overrides. NEUMORPHIC_CSS = f""" /* ── Reset & base ─────────────────────────────────────────────────────── */ gradio-app {{ background: {BG} !important; }} /* ── Block / panel containers — raised ────────────────────────────────── */ .gradio-container .block, .gradio-container .form, .gradio-container .gap {{ background: {BG} !important; border: none !important; border-radius: 14px !important; box-shadow: -6px -6px 12px {LIGHT_SHDW}, 6px 6px 12px {DARK_SHDW} !important; }} /* ── Inputs / textareas — recessed (inset shadow) ──────────────────────── */ .gradio-container input[type="text"], .gradio-container input[type="number"], .gradio-container input[type="search"], .gradio-container textarea, .gradio-container select {{ background: {INPUT_BG} !important; border: none !important; border-radius: 10px !important; box-shadow: inset -3px -3px 7px {LIGHT_SHDW}, inset 3px 3px 7px {DARK_SHDW} !important; color: {TEXT_DARK} !important; transition: box-shadow 0.2s ease !important; }} .gradio-container input[type="text"]:focus, .gradio-container input[type="number"]:focus, .gradio-container textarea:focus, .gradio-container select:focus {{ box-shadow: inset -2px -2px 5px {LIGHT_SHDW}, inset 2px 2px 5px {DARK_SHDW}, 0 0 0 2px rgba(204,213,174,0.55) !important; outline: none !important; }} /* ── Sliders — track recessed, thumb raised ────────────────────────────── */ .gradio-container input[type="range"] {{ -webkit-appearance: none; appearance: none; background: transparent !important; border: none !important; box-shadow: none !important; }} .gradio-container input[type="range"]::-webkit-slider-runnable-track {{ height: 6px; border-radius: 3px; background: {INPUT_BG}; box-shadow: inset -2px -2px 4px {LIGHT_SHDW}, inset 2px 2px 4px {DARK_SHDW}; border: none; }} .gradio-container input[type="range"]::-webkit-slider-thumb {{ -webkit-appearance: none; width: 18px; height: 18px; border-radius: 50%; background: {BG}; margin-top: -6px; box-shadow: -3px -3px 6px {LIGHT_SHDW}, 3px 3px 6px {DARK_SHDW}; border: none; cursor: pointer; transition: box-shadow 0.15s ease; }} .gradio-container input[type="range"]::-webkit-slider-thumb:hover {{ box-shadow: -4px -4px 8px {LIGHT_SHDW}, 4px 4px 8px {DARK_SHDW}, 0 0 0 3px rgba(204,213,174,0.45); }} .gradio-container input[type="range"]::-moz-range-track {{ height: 6px; border-radius: 3px; background: {INPUT_BG}; box-shadow: inset -2px -2px 4px {LIGHT_SHDW}, inset 2px 2px 4px {DARK_SHDW}; border: none; }} .gradio-container input[type="range"]::-moz-range-thumb {{ width: 18px; height: 18px; border-radius: 50%; background: {BG}; box-shadow: -3px -3px 6px {LIGHT_SHDW}, 3px 3px 6px {DARK_SHDW}; border: none; cursor: pointer; }} /* ── Checkboxes ─────────────────────────────────────────────────────────── */ .gradio-container input[type="checkbox"] {{ -webkit-appearance: none; appearance: none; width: 18px; height: 18px; border-radius: 5px; background: {BG}; box-shadow: inset -2px -2px 5px {LIGHT_SHDW}, inset 2px 2px 5px {DARK_SHDW}; border: none; cursor: pointer; transition: all 0.15s ease; }} .gradio-container input[type="checkbox"]:checked {{ background: {ACCENT}; box-shadow: inset -2px -2px 5px rgba(255,255,255,0.3), inset 2px 2px 5px rgba(0,0,0,0.15); }} /* ── Buttons — primary (raised, pressed on active) ─────────────────────── */ .gradio-container .btn, .gradio-container button.primary, .gradio-container button[variant="primary"] {{ background: {BG} !important; border: none !important; border-radius: 10px !important; box-shadow: -5px -5px 10px {LIGHT_SHDW}, 5px 5px 10px {DARK_SHDW} !important; color: {TEXT_DARK} !important; font-weight: 500 !important; transition: box-shadow 0.15s ease, transform 0.1s ease !important; }} .gradio-container button.primary, .gradio-container button[variant="primary"] {{ background: linear-gradient(145deg, {ACCENT}, #b8c49a) !important; color: {TEXT_DARK} !important; }} .gradio-container .btn:hover, .gradio-container button:hover {{ box-shadow: -7px -7px 14px {LIGHT_SHDW}, 7px 7px 14px {DARK_SHDW} !important; }} .gradio-container .btn:active, .gradio-container button:active {{ box-shadow: inset -3px -3px 7px {LIGHT_SHDW}, inset 3px 3px 7px {DARK_SHDW} !important; transform: scale(0.985) !important; }} /* ── Stop / cancel button ──────────────────────────────────────────────── */ .gradio-container button[variant="stop"], .gradio-container button.stop {{ background: linear-gradient(145deg, #e8c9a0, #d4b080) !important; }} /* ── Tabs ──────────────────────────────────────────────────────────────── */ .gradio-container .tabs > .tab-nav {{ border-bottom: none !important; gap: 8px; }} .gradio-container .tabs > .tab-nav > button {{ background: {BG} !important; border: none !important; border-radius: 10px !important; box-shadow: -4px -4px 8px {LIGHT_SHDW}, 4px 4px 8px {DARK_SHDW} !important; color: {TEXT_MID} !important; padding: 8px 18px !important; transition: all 0.15s ease !important; }} .gradio-container .tabs > .tab-nav > button.selected {{ background: {BG} !important; box-shadow: inset -3px -3px 7px {LIGHT_SHDW}, inset 3px 3px 7px {DARK_SHDW} !important; color: {TEXT_DARK} !important; font-weight: 500 !important; }} /* ── Accordion ─────────────────────────────────────────────────────────── */ .gradio-container .accordion {{ border: none !important; border-radius: 12px !important; box-shadow: -5px -5px 10px {LIGHT_SHDW}, 5px 5px 10px {DARK_SHDW} !important; overflow: hidden; }} .gradio-container .accordion > .label-wrap {{ background: {BG} !important; }} /* ── Image output panel ────────────────────────────────────────────────── */ .gradio-container .image-container, .gradio-container .output-image {{ border: none !important; border-radius: 14px !important; box-shadow: inset -4px -4px 10px {LIGHT_SHDW}, inset 4px 4px 10px {DARK_SHDW} !important; overflow: hidden; }} /* ── Labels & typography ───────────────────────────────────────────────── */ .gradio-container label span, .gradio-container .block-label, .gradio-container .label-wrap span {{ color: {TEXT_MID} !important; font-weight: 500 !important; font-size: 13px !important; letter-spacing: 0.02em !important; }} .gradio-container .prose h1, .gradio-container .prose h2, .gradio-container .prose h3 {{ color: {TEXT_DARK} !important; }} .gradio-container .prose p, .gradio-container .prose li {{ color: {TEXT_MID} !important; }} /* ── Scrollbars ────────────────────────────────────────────────────────── */ .gradio-container ::-webkit-scrollbar {{ width: 6px; height: 6px; }} .gradio-container ::-webkit-scrollbar-track {{ background: {INPUT_BG}; border-radius: 3px; box-shadow: inset 0 0 4px {DARK_SHDW}; }} .gradio-container ::-webkit-scrollbar-thumb {{ background: {ACCENT}; border-radius: 3px; }} """ # ── Gradio colour ramp (required by Base theme) ────────────────────────────── sage_sand = colors.Color( name="sage_sand", c50="#f7f5ec", c100="#FEFAE0", c200="#FAEDCD", c300="#E9EDC9", c400="#CCD5AE", c500="#b8c49a", c600="#a0ad80", c700="#D4A373", c800="#b8895a", c900="#8a6140", c950="#5c3d24", ) class SageAndSandNeumorphic(Base): """ Sage & Sand Neumorphic — a soft-extruded Gradio theme. Pair with NEUMORPHIC_CSS for the full effect: import gradio as gr from theme import SageAndSandNeumorphic, NEUMORPHIC_CSS with gr.Blocks(theme=SageAndSandNeumorphic(), css=NEUMORPHIC_CSS) as demo: ... """ def __init__( self, primary_hue: colors.Color = sage_sand, secondary_hue: colors.Color = sage_sand, neutral_hue: colors.Color = sage_sand, spacing_size: sizes.Size = sizes.spacing_md, radius_size: sizes.Size = sizes.radius_lg, text_size: sizes.Size = sizes.text_md, font=( fonts.GoogleFont("DM Sans"), "ui-sans-serif", "system-ui", "sans-serif", ), font_mono=( fonts.GoogleFont("JetBrains Mono"), "ui-monospace", "monospace", ), ): super().__init__( primary_hue=primary_hue, secondary_hue=secondary_hue, neutral_hue=neutral_hue, spacing_size=spacing_size, radius_size=radius_size, text_size=text_size, font=font, font_mono=font_mono, ) super().set( # ── Page background ───────────────────────────────────────────── body_background_fill=BG, body_background_fill_dark="#8a6140", # ── Blocks (base token — CSS overrides the visual shadow) ──────── block_background_fill=BG, block_background_fill_dark="#a07040", block_border_width="0px", block_border_color="transparent", block_shadow=f"-6px -6px 12px {LIGHT_SHDW}, 6px 6px 12px {DARK_SHDW}", block_shadow_dark="none", # Panel panel_background_fill=BG, panel_background_fill_dark="#9a7040", panel_border_width="0px", # ── Block labels ───────────────────────────────────────────────── block_label_background_fill="transparent", block_label_background_fill_dark="transparent", block_label_text_color=TEXT_MID, block_label_text_color_dark=CREAM, block_label_text_size="*text_sm", block_label_text_weight="500", block_title_text_color=TEXT_DARK, block_title_text_color_dark=CREAM, block_title_text_weight="600", # ── Inputs ─────────────────────────────────────────────────────── input_background_fill=INPUT_BG, input_background_fill_dark="#9a7040", input_background_fill_focus=INPUT_BG, input_border_width="0px", input_border_color="transparent", input_shadow=f"inset -3px -3px 7px {LIGHT_SHDW}, inset 3px 3px 7px {DARK_SHDW}", input_shadow_focus=f"inset -2px -2px 5px {LIGHT_SHDW}, inset 2px 2px 5px {DARK_SHDW}, 0 0 0 2px rgba(204,213,174,0.55)", input_placeholder_color=TEXT_MUTED, input_placeholder_color_dark="#c8a878", # ── Slider ─────────────────────────────────────────────────────── slider_color=ACCENT, slider_color_dark=ACCENT2, # ── Checkboxes ─────────────────────────────────────────────────── checkbox_background_color=BG, checkbox_background_color_dark="#a07040", checkbox_background_color_selected=ACCENT, checkbox_background_color_selected_dark="#a0ad80", checkbox_border_width="0px", checkbox_border_color="transparent", checkbox_label_background_fill=BG, checkbox_label_background_fill_dark="#9a7040", checkbox_label_background_fill_hover=BG, checkbox_label_background_fill_selected=ACCENT, checkbox_label_background_fill_selected_dark="#a0ad80", # ── Buttons ────────────────────────────────────────────────────── button_primary_background_fill=f"linear-gradient(145deg, {ACCENT}, #b8c49a)", button_primary_background_fill_dark=f"linear-gradient(145deg, #a0ad80, #8a9668)", button_primary_background_fill_hover=f"linear-gradient(145deg, #b8c49a, {ACCENT})", button_primary_text_color=TEXT_DARK, button_primary_text_color_dark=CREAM, button_primary_border_color="transparent", button_primary_shadow=f"-5px -5px 10px {LIGHT_SHDW}, 5px 5px 10px {DARK_SHDW}", button_primary_shadow_hover=f"-7px -7px 14px {LIGHT_SHDW}, 7px 7px 14px {DARK_SHDW}", button_primary_shadow_active=f"inset -3px -3px 7px {LIGHT_SHDW}, inset 3px 3px 7px {DARK_SHDW}", button_transition="box-shadow 0.15s ease, transform 0.1s ease", button_secondary_background_fill=BG, button_secondary_background_fill_dark="#9a7040", button_secondary_background_fill_hover=BG, button_secondary_text_color=TEXT_MID, button_secondary_text_color_dark=CREAM, button_secondary_border_color="transparent", button_cancel_background_fill=f"linear-gradient(145deg, #e8c9a0, #d4b080)", button_cancel_background_fill_dark=f"linear-gradient(145deg, #8a6140, #7a5130)", button_cancel_text_color=TEXT_DARK, button_cancel_text_color_dark=CREAM, button_large_padding="10px 22px", button_small_padding="5px 12px", # ── Tables ─────────────────────────────────────────────────────── table_even_background_fill=INPUT_BG, table_even_background_fill_dark="#9a7040", table_odd_background_fill=BG, table_odd_background_fill_dark="#a07040", table_border_color="transparent", table_row_focus=ACCENT2, table_row_focus_dark="#b8895a", # ── Typography ─────────────────────────────────────────────────── body_text_color=TEXT_DARK, body_text_color_dark=CREAM, body_text_color_subdued=TEXT_MUTED, body_text_color_subdued_dark="#d4c8a8", link_text_color="#7a8a5a", link_text_color_dark=ACCENT, link_text_color_hover="#5a6a3a", link_text_color_hover_dark=ACCENT2, # ── Code ───────────────────────────────────────────────────────── code_background_fill=INPUT_BG, code_background_fill_dark="#8a6140", # ── Error / status ──────────────────────────────────────────────── error_background_fill="#e8c8a8", error_background_fill_dark="#7a4820", error_border_color="transparent", error_text_color="#7a3820", error_text_color_dark="#f5d0b0", ) # ── Convenience instance ───────────────────────────────────────────────────── theme = SageAndSandNeumorphic() # ── Quick preview ───────────────────────────────────────────────────────────── if __name__ == "__main__": with gr.Blocks( theme=SageAndSandNeumorphic(), css=NEUMORPHIC_CSS, title="Sage & Sand Neumorphic — Preview", ) as demo: gr.Markdown("## Sage & Sand Neumorphic") gr.Markdown("A soft-extruded earthy theme for Stable Diffusion WebUI.") with gr.Row(): with gr.Column(): prompt = gr.Textbox(label="Prompt", lines=3, placeholder="A serene landscape…") neg_prompt = gr.Textbox(label="Negative prompt", lines=2, placeholder="blurry, watermark…") with gr.Row(): steps = gr.Slider(1, 150, value=20, step=1, label="Steps") cfg = gr.Slider(1, 30, value=7, step=0.5, label="CFG Scale") with gr.Row(): sampler = gr.Dropdown(["Euler a", "DPM++ 2M", "DDIM"], value="Euler a", label="Sampler") faces = gr.Checkbox(label="Restore faces") with gr.Row(): gr.Button("Generate", variant="primary") gr.Button("Interrupt", variant="stop") with gr.Column(): gr.Image(label="Output", height=320) with gr.Tabs(): with gr.Tab("Generation"): with gr.Row(): gr.Slider(512, 2048, value=512, step=64, label="Width") gr.Slider(512, 2048, value=512, step=64, label="Height") gr.Slider(1, 8, value=1, step=1, label="Batch size") with gr.Tab("ControlNet"): gr.Textbox(label="ControlNet model", placeholder="control_v11p_sd15_openpose") gr.Slider(0, 2, value=1, step=0.05, label="Control weight") with gr.Tab("Extras"): gr.Textbox(label="Notes", lines=4) demo.launch()