Class: Fresco::Welcome

Inherits:
Action
  • Object
show all
Defined in:
lib/fresco/runtime/welcome.rb

Instance Attribute Summary

Attributes inherited from Action

#req

Instance Method Summary collapse

Methods inherited from Action

#after_action, #before_action, #halt!, #handle, #initialize, #layout, #redirect_to, #render

Constructor Details

This class inherits a constructor from Fresco::Action

Instance Method Details

#call(req = Request.new("", "", "")) ⇒ Object



21
22
23
# File 'lib/fresco/runtime/welcome.rb', line 21

def call(req = Request.new("", "", ""))
  Response.html(200, welcome_body)
end

#welcome_bodyObject

Manual string concat (matches build_headers / Json.encode_string in runtime.rb) keeps each intermediate narrowly typed String for Spinel — heredocs and ‘<<~`-style literals don’t appear elsewhere in the Spinel-compiled surface, so we stick to the established ‘out += …` pattern.



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/fresco/runtime/welcome.rb', line 30

def welcome_body
  out = "<!doctype html>"
  out += "<html>"
  out += "<head>"
  out += "<meta charset=\"utf-8\">"
  out += "<title>Welcome to Fresco</title>"
  out += "<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">"
  out += "<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>"
  out += "<link href=\"https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;700&display=swap\" rel=\"stylesheet\">"
  out += "<script>"
  out += "(function () {"
  out += "  var t = localStorage.getItem(\"theme\");"
  out += "  if (!t) {"
  out += "    t = (window.matchMedia && window.matchMedia(\"(prefers-color-scheme: dark)\").matches) ? \"dark\" : \"light\";"
  out += "    localStorage.setItem(\"theme\", t);"
  out += "  }"
  out += "  document.documentElement.setAttribute(\"data-theme\", t);"
  out += "})();"
  out += "</script>"
  out += "<style>"
  out += ":root { --bg: #f5f5f4; --fg: #1c1917; --muted: #78716c; --border: #e7e5e4; --code-bg: #e7e5e4; --accent: #0066cc; --status: #78716c; }"
  out += ":root[data-theme=\"dark\"] { --bg: #1c1917; --fg: #e7e5e4; --muted: #a8a29e; --border: #292524; --code-bg: #292524; --accent: #6ab0ff; --status: #a8a29e; }"
  out += "body { font-family: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace; max-width: 40rem; margin: 4rem auto; padding: 0 1.5rem; background: var(--bg); color: var(--fg); transition: background 0.15s, color 0.15s; }"
  out += "h1 { font-weight: 700; font-size: 2.2rem; margin: 0 0 0.5rem; line-height: 1.2; }"
  out += ".status { color: var(--status); font-size: 0.9rem; letter-spacing: 0.05em; text-transform: uppercase; }"
  out += ".lede { color: var(--muted); margin: 0 0 1.75rem; }"
  out += "p { line-height: 1.5; }"
  out += "code { background: var(--code-bg); padding: 0.1rem 0.3rem; border-radius: 3px; }"
  out += "a { color: var(--accent); }"
  out += "a:hover { text-decoration: underline; }"
  out += "hr { border: none; border-top: 1px solid var(--border); margin: 2rem 0; }"
  out += "footer { color: var(--muted); font-size: 0.85rem; }"
  out += "#theme-toggle { position: fixed; top: 1rem; right: 1rem; background: transparent; border: 1px solid var(--border); color: var(--fg); border-radius: 6px; padding: 0.4rem; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }"
  out += "#theme-toggle:hover { background: var(--code-bg); }"
  out += "#theme-toggle svg { width: 18px; height: 18px; display: none; }"
  out += ":root[data-theme=\"light\"] #theme-toggle .icon-moon { display: inline-block; }"
  out += ":root[data-theme=\"dark\"]  #theme-toggle .icon-sun  { display: inline-block; }"
  # Trans pride coloring on the "Fresco" wordmark — 6 letters laid
  # out as a mirror of the 5-stripe flag (blue / pink / white /
  # pink / blue). The two middle letters take --fg as a stand-in
  # for "white" so they stay readable on both stone backgrounds
  # (renders as dark stone in light mode, near-white in dark mode).
  # Light mode uses deeper blue/pink variants so they don't wash
  # out against #f5f5f4; dark mode uses the canonical pastels which
  # pop on #1c1917.
  out += ".pride { font-weight: 700; letter-spacing: 0.04em; margin-left: 0.1em; }"
  out += ".pride span { display: inline-block; }"
  out += ".pride .p1 { color: #2dbfe0; }"
  out += ".pride .p2 { color: #ed7b95; }"
  out += ".pride .p3 { color: var(--fg); }"
  out += ".pride .p4 { color: var(--fg); }"
  out += ".pride .p5 { color: #ed7b95; }"
  out += ".pride .p6 { color: #2dbfe0; }"
  out += ":root[data-theme=\"dark\"] .pride .p1 { color: #5bcefa; }"
  out += ":root[data-theme=\"dark\"] .pride .p2 { color: #f5a9b8; }"
  out += ":root[data-theme=\"dark\"] .pride .p5 { color: #f5a9b8; }"
  out += ":root[data-theme=\"dark\"] .pride .p6 { color: #5bcefa; }"
  # Two CTA cards (Docs / GitHub). Whole card is the clickable <a>;
  # the icon, title, and "→" sit stacked inside. Flex layout, equal
  # width, collapses to a single column on narrow viewports so the
  # cards don't squeeze on mobile.
  out += ".cards { display: flex; gap: 0.875rem; margin: 0 0 1.75rem; }"
  out += ".cta { flex: 1; display: flex; flex-direction: column; gap: 0.5rem; padding: 1.25rem; border: 2px solid var(--border); border-radius: 8px; background: transparent; color: var(--fg); text-decoration: none; transition: transform 0.15s, border-color 0.15s; }"
  # Trans pride hover border: two-layer background trick. The inner
  # solid layer is clipped to padding-box and covers the card body;
  # the gradient layer fills border-box and only shows through where
  # the (now-transparent) border sits. The gradient encodes the
  # 5-stripe flag (blue / pink / white / pink / blue) doubled
  # end-to-end so panning 200% bg-size from 0 to -100% loops
  # seamlessly — the second half is identical to the first.
  out += ".cta:hover { border-color: transparent; transform: translateY(-1px); background-image: linear-gradient(var(--code-bg), var(--code-bg)), linear-gradient(90deg, #5bcefa, #f5a9b8, #ffffff, #f5a9b8, #5bcefa, #f5a9b8, #ffffff, #f5a9b8, #5bcefa); background-size: auto, 200% 100%; background-position: 0 0, 0 0; background-origin: border-box; background-clip: padding-box, border-box; animation: prideShift 4s linear infinite; }"
  out += "@keyframes prideShift { to { background-position: 0 0, -100% 0; } }"
  out += ".cta svg { width: 28px; height: 28px; stroke: var(--accent); }"
  out += ".cta-title { font-weight: 700; font-size: 1.05rem; }"
  out += ".cta-sub { color: var(--muted); font-size: 0.88rem; }"
  out += ".cta:hover .cta-sub { color: var(--accent); }"
  out += "@media (max-width: 30rem) { .cards { flex-direction: column; } }"
  # Respect the OS reduce-motion preference — keep the rainbow
  # border, just stop panning it for folks who opted out.
  out += "@media (prefers-reduced-motion: reduce) { .cta:hover { animation: none; } }"
  out += "</style>"
  out += "</head>"
  out += "<body>"
  out += "<button id=\"theme-toggle\" aria-label=\"Toggle color theme\" title=\"Toggle color theme\">"
  out += "<svg class=\"icon-sun\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2\"/><path d=\"M12 20v2\"/><path d=\"m4.93 4.93 1.41 1.41\"/><path d=\"m17.66 17.66 1.41 1.41\"/><path d=\"M2 12h2\"/><path d=\"M20 12h2\"/><path d=\"m6.34 17.66-1.41 1.41\"/><path d=\"m19.07 4.93-1.41 1.41\"/></svg>"
  out += "<svg class=\"icon-moon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"/></svg>"
  out += "</button>"
  out += "<div class=\"status\">200 &middot; Welcome</div>"
  out += "<h1>You're on <span class=\"pride\"><span class=\"p1\">F</span><span class=\"p2\">r</span><span class=\"p3\">e</span><span class=\"p4\">s</span><span class=\"p5\">c</span><span class=\"p6\">o</span></span>.</h1>"
  out += "<p class=\"lede\">Spinel-compiled Ruby. Single binary. No runtime.</p>"
  # Lucide icons (MIT) inlined as SVG. book-open → Docs;
  # github → repo. Update href="#" with real URLs when ready.
  out += "<div class=\"cards\">"
  out += "<a class=\"cta\" href=\"https://github.com/afomera/fresco\">"
  out += "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 7v14\"/><path d=\"M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z\"/></svg>"
  out += "<div class=\"cta-title\">Documentation</div>"
  out += "<div class=\"cta-sub\">Get started &rarr;</div>"
  out += "</a>"
  out += "<a class=\"cta\" href=\"https://github.com/afomera/fresco\">"
  out += "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4\"/><path d=\"M9 18c-4.51 2-5-2-7-2\"/></svg>"
  out += "<div class=\"cta-title\">GitHub</div>"
  out += "<div class=\"cta-sub\">View source &rarr;</div>"
  out += "</a>"
  out += "</div>"
  out += "<p>Next step: add <code>root to: SomeAction</code> to <code>config/routes.rb</code>, then rebuild and reload.</p>"
  out += "<hr>"
  out += "<footer>Powered by Spinel + Fresco. Served from the fresco gem's <code>welcome</code> action.</footer>"
  out += "<script>"
  out += "(function () {"
  out += "  document.getElementById(\"theme-toggle\").addEventListener(\"click\", function () {"
  out += "    var cur = document.documentElement.getAttribute(\"data-theme\");"
  out += "    var nxt = cur === \"dark\" ? \"light\" : \"dark\";"
  out += "    document.documentElement.setAttribute(\"data-theme\", nxt);"
  out += "    localStorage.setItem(\"theme\", nxt);"
  out += "  });"
  out += "})();"
  out += "</script>"
  out += "</body>"
  out += "</html>"
  out
end