This website is built entirely using Java

Objectos Way allows you to create complete web applications using nothing but the Java programming language.

Live Objectos Way Demo

This demo is written entirely in Java using Objectos Way, JDK 23 and the H2 database engine.
The main panel displays the application itself, while the secondary panel shows the source code used to generate the view.

Comedy of Errors 2.0

A case of mistaken identity in the age of social media leads to hilarious consequences for two identical strangers.

Rating
N/A
Runtime
1h 33m
Release date
Dec 28, 2024
Genres
Comedy
Comedy of Errors 2.0

Showtimes

Fri 02/May

Screen 2

Closed Caption, Audio Description, Recliner
Sat 03/May

Screen 2

Closed Caption, Audio Description, Recliner
/* * Copyright (C) 2024-2025 Objectos Software LTDA. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package demo.landing.app;import java.time.LocalDateTime;import java.util.List;import java.util.Optional;import objectos.way.Html;import objectos.way.Http;import objectos.way.Sql;final class Movie implements Kino.GET {  private final Kino.Ctx ctx;  Movie(Kino.Ctx ctx) {    this.ctx = ctx;  }  @Override  public final Html.Component get(Http.Exchange http) {    final Sql.Transaction trx;    trx = http.get(Sql.Transaction.class);    final Kino.Query query;    query = http.get(Kino.Query.class);    final int movieId;    movieId = query.idAsInt();    final Optional<MovieDetails> maybeDetails;    maybeDetails = MovieDetails.queryOptional(trx, movieId);    if (maybeDetails.isEmpty()) {      return NotFound.create();    }    final MovieDetails details;    details = maybeDetails.get();    final LocalDateTime today;    today = ctx.today();    final List<MovieScreening> screenings;    screenings = MovieScreening.query(trx, movieId, today);    return Shell.create(shell -> {      shell.appFrame = shell.sourceFrame = "movie-" + movieId;      shell.app = new MovieView(ctx, details, screenings);      shell.sources(          Source.Movie,          Source.MovieDetails,          Source.MovieScreening,          Source.MovieView      );    });  }}
/* * Copyright (C) 2024-2025 Objectos Software LTDA. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package demo.landing.app;import java.sql.ResultSet;import java.sql.SQLException;import java.util.Optional;import objectos.way.Sql;record MovieDetails(    int movieId,    String title,    String runtime,    String releaseDate,    String genres,    String synopsys) {  MovieDetails(ResultSet rs, int idx) throws SQLException {    this(        rs.getInt(idx++),        rs.getString(idx++),        rs.getString(idx++),        rs.getString(idx++),        rs.getString(idx++),        rs.getString(idx++)    );  }  public static Optional<MovieDetails> queryOptional(Sql.Transaction trx, int id) {    trx.sql("""    select      MOVIE.MOVIE_ID,      MOVIE.TITLE,      concat (MOVIE.RUNTIME / 60, 'h ', MOVIE.RUNTIME % 60, 'm'),      formatdatetime (MOVIE.RELEASE_DATE, 'MMM dd, yyyy'),      listagg (GENRE.NAME, ', ') within group (        order by          GENRE.NAME      ),      MOVIE.SYNOPSYS    from      MOVIE      natural join MOVIE_GENRE      natural join GENRE    where      MOVIE.MOVIE_ID = ?    group by      MOVIE.MOVIE_ID    """);    trx.add(id);    return trx.queryOptional(MovieDetails::new);  }}
/* * Copyright (C) 2024-2025 Objectos Software LTDA. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package demo.landing.app;import java.sql.Array;import java.sql.ResultSet;import java.sql.SQLException;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.LocalTime;import java.util.ArrayList;import java.util.List;import objectos.way.Sql;record MovieScreening(    int screenId,    String screenName,    String features,    String date,    List<Showtime> showtimes) {  record Showtime(int showId, String time) {    Showtime(Object[] row) {      this(          Integer.parseInt(row[0].toString()),          row[1].toString()      );    }    static List<Showtime> of(Array array) throws SQLException {      Object[] values;      values = (Object[]) array.getArray();      List<Showtime> list;      list = new ArrayList<>(values.length);      for (Object value : values) {        Array inner;        inner = (Array) value;        Object[] row;        row = (Object[]) inner.getArray();        Showtime showtime;        showtime = new Showtime(row);        list.add(showtime);      }      return List.copyOf(list);    }  }  MovieScreening(ResultSet rs, int idx) throws SQLException {    this(        rs.getInt(idx++),        rs.getString(idx++),        rs.getString(idx++),        rs.getString(idx++),        Showtime.of(rs.getArray(idx++))    );  }  public static List<MovieScreening> query(Sql.Transaction trx, int id, LocalDateTime dateTime) {    trx.sql("""    select      SCREEN.SCREEN_ID,      SCREEN.NAME,      (        select          listagg (FEATURE.NAME, ', ') within group (order by FEATURE.FEATURE_ID)        from          SCREENING_FEATURE          natural join FEATURE        where          SCREENING_FEATURE.SCREENING_ID = SCREENING.SCREENING_ID        group by          SCREENING_FEATURE.SCREENING_ID      ) as F,      formatdatetime(SHOW.SHOWDATE, 'EEE dd/LLL'),      array_agg (        array [cast(SHOW.SHOW_ID as varchar), formatdatetime(SHOW.SHOWTIME, 'kk:mm')]        order by SHOW.SHOWTIME      )    from      SHOW      natural join SCREENING      natural join SCREEN    where      SCREENING.MOVIE_ID = ?      and (        (          SHOW.SHOWDATE = ?          and SHOW.SHOWTIME > ?        )        or SHOW.SHOWDATE > ?      )    group by      SHOW.SHOWDATE,      SCREEN.SCREEN_ID    order by      SHOW.SHOWDATE,      SCREEN.SCREEN_ID    """);    trx.add(id);    final LocalDate date;    date = dateTime.toLocalDate();    trx.add(date);    final LocalTime time;    time = dateTime.toLocalTime();    trx.add(time);    trx.add(date);    return trx.query(MovieScreening::new);  }}
/* * Copyright (C) 2024-2025 Objectos Software LTDA. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package demo.landing.app;import java.util.List;import objectos.way.Css;/** * Movie details and screening selection view. */@Css.Sourcefinal class MovieView extends Kino.View {  private final Kino.Ctx ctx;  private final MovieDetails details;  private final List<MovieScreening> screenings;  MovieView(Kino.Ctx ctx, MovieDetails details, List<MovieScreening> screenings) {    this.ctx = ctx;    this.details = details;    this.screenings = screenings;  }  @Override  protected final void render() {    backLink(ctx, Kino.Page.NOW_SHOWING);    div(        css("""        display:grid        gap:16rx        grid-template-columns:1fr_160rx        """),        renderFragment(this::renderMovieDetails)    );    div(        css("""        display:flex        flex-direction:column        gap:16rx        padding:64rx_0_0        """),        renderFragment(this::renderMovieScreenings)    );  }  private void renderMovieDetails() {    div(        h2(            text(testableH1(details.title()))        ),        p(            text(testableField("synopsys", details.synopsys()))        ),        dl(            css("""            display:grid            font-size:14rx            grid-template-columns:repeat(2,1fr)            dt:font-weight:600            dt:padding-top:12rx            """),            div(                dt(                    text("Rating")                ),                dd(                    text("N/A")                )            ),            div(                dt(                    text("Runtime")                ),                dd(                    text(testableField("runtime", details.runtime()))                )            ),            div(                dt(                    text("Release date")                ),                dd(                    text(testableField("release-date", details.releaseDate()))                )            ),            div(                dt(                    text("Genres")                ),                dd(                    text(testableField("genres", details.genres()))                )            )        )    );    div(        css("""        padding-top:16rx        """),        img(            css("""            border-radius:6rx            width:100%            """),            alt(details.title()),            src("/demo/landing/poster" + details.movieId() + ".jpg")        )    );  }  private void renderMovieScreenings() {    h3(        css("""        font-size:24rx        font-weight:300        line-height:1        """),        text(testableH1("Showtimes"))    );    for (MovieScreening screening : screenings) {      div(          css("""          border:1px_solid_border          display:flex          gap:32rx          padding:16rx          position:relative          """),          div(              css("""              align-items:center              display:flex              flex-direction:column              gap:8rx              justify-content:center              """),              icon(                  Kino.Icon.CALENDAR_CHECK,                  css("""                  stroke:icon                  """)              ),              span(                  css("""                  text-align:center                  width:6rch                  """),                  text(testableH2(screening.date()))              )          ),          div(              h4(                  css("""                  font-weight:500                  line-height:1                  padding-top:8rx                  """),                  text(testableField("screen", screening.screenName()))              ),              div(                  css("""                  font-size:14rx                  font-weight:300                  padding:8rx_0                  """),                  text(testableField("features", screening.features()))              ),              ul(                  css("""                  display:flex                  flex-wrap:wrap                  gap:12rx                  """),                  testableNewLine(),                  renderFragment(this::renderShowtimes, screening.showtimes())              )          )      );    }  }  private void renderShowtimes(List<MovieScreening.Showtime> showtimes) {    for (MovieScreening.Showtime showtime : showtimes) {      final int showId;      showId = showtime.showId();      final Kino.Query query;      query = Kino.Page.SEATS.query(showId);      testableCell(query.toString(), 32);      final String time;      time = showtime.time();      li(          a(              css("""              border:1px_solid_border              border-radius:9999rx              display:flex              padding:8rx_16rx              active:background-color:btn-ghost-active              hover:background-color:btn-ghost-hover              """),              dataOnClick(this::navigate),              href(ctx.href(query)),              rel("nofollow"),              span(testableCell(time, 5))          ),          testableNewLine()      );    }  }}
/* * Copyright (C) 2024-2025 Objectos Software LTDA. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package demo.landing.app;import demo.landing.LandingDemo;import demo.landing.LandingDemoConfig;import java.time.Clock;import java.time.LocalDateTime;import java.util.Objects;import objectos.way.Css;import objectos.way.Html;import objectos.way.Http;import objectos.way.Note;import objectos.way.Script;import objectos.way.Sql;/** * Demo entry point. */public final class Kino implements LandingDemo {  private final Ctx ctx;  private Kino(Ctx ctx) {    this.ctx = ctx;  }  /**   * Creates a new {@code Demo} instance with the specified configuration.   */  public static Kino create(LandingDemoConfig config) {    Objects.requireNonNull(config, "config == null");    final Ctx ctx;    ctx = Ctx.of(config);    return new Kino(ctx);  }  /**   * Handles a GET request.   *   * <p>   * Typically this would be a {@code Http.Handler} instance. However, as this   * will be embedded in another application, we return a HTML component   * instead.   */  @Override  public final Html.Component get(Http.Exchange http) {    final Query query;    query = ctx.decode(http);    // based on the 'demo' value we create our controller    final GET controller;    controller = switch (query.page) {      case CONFIRM -> new Confirm(ctx);      case MOVIE -> new Movie(ctx);      case NOW_SHOWING -> new NowShowing(ctx);      case SEATS -> new Seats(ctx);      case TICKET -> new Ticket();      case BAD_REQUEST -> new NotFound();    };    // we intercept all controllers even though    // not all require DB access strictly speaking    return ctx.transactional(http, controller);  }  /**   * Handles a POST request.   */  @Override  public final Kino.PostResult post(Http.Exchange http) {    final Query query;    query = ctx.decode(http);    final POST controller;    controller = switch (query.page) {      case CONFIRM -> new Confirm(ctx);      case SEATS -> new Seats(ctx);      default -> new NotFound();    };    return ctx.transactional(http, controller);  }  //  // UI related classes  //  /**   * SVG icons from the Lucide project.   */  enum Icon {    ARROW_LEFT("""    <path d="m12 19-7-7 7-7"/><path d="M19 12H5"/>"""),    CALENDAR_CHECK("""    <path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/><path d="m9 16 2 2 4-4"/>"""),    CLOCK("""    <circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>"""),    CREDIT_CARD("""    <rect width="20" height="14" x="2" y="5" rx="2"/><line x1="2" x2="22" y1="10" y2="10"/>"""),    FILM(        """    <rect width="18" height="18" x="3" y="3" rx="2"/><path d="M7 3v18"/><path d="M3 7.5h4"/><path d="M3 12h18"/><path d="M3 16.5h4"/><path d="M17 3v18"/><path d="M17 7.5h4"/><path d="M17 16.5h4"/>"""),    FROWN("""    <circle cx="12" cy="12" r="10"/><path d="M16 16s-1.5-2-4-2-4 2-4 2"/><line x1="9" x2="9.01" y1="9" y2="9"/><line x1="15" x2="15.01" y1="9" y2="9"/>"""),    INFO("""    <circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>"""),    PROJECTOR(        """    <path d="M5 7 3 5"/><path d="M9 6V3"/><path d="m13 7 2-2"/><circle cx="9" cy="13" r="3"/><path d="M11.83 12H20a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h2.17"/><path d="M16 16h2"/>"""),    RECEIPT("""    <path d="M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1Z"/><path d="M16 8h-6a2 2 0 1 0 0 4h4a2 2 0 1 1 0 4H8"/><path d="M12 17.5v-11"/>"""),    TICKET("""    <path d="M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"/><path d="M13 5v2"/><path d="M13 17v2"/><path d="M13 11v2"/>""");    final String contents;    Icon(String contents) {      this.contents = contents;    }  }  /**   * Base HTML template of the application. Provides a utility methods for   * rendering UI fragments common to the application.   */  @Css.Source // Indicates to the CSS Generator that it should scan this class for CSS utilities.  static abstract class View extends Html.Template {    static final Html.ClassName PRIMARY = Html.ClassName.ofText("""    appearance:none    background-color:btn-primary    color:btn-primary-text    cursor:pointer    display:flex    font-size:14rx    min-height:48rx    padding:14rx_63rx_14rx_15rx    active:background-color:btn-primary-active    hover:background-color:btn-primary-hover    """);    //    // component methods    //    /**     * Renders the "Go Back" link.     */    final Html.Instruction.OfElement backLink(Ctx ctx, Page page) {      testableField("back-link", page.name());      return backLink(ctx.href(page));    }    /**     * Renders the "Go Back" link.     */    final Html.Instruction.OfElement backLink(Ctx ctx, Page page, long id) {      testableField("back-link", page.name() + ":" + id);      return backLink(ctx.href(page, id));    }    /**     * Renders the "Go Back" link.     */    final Html.Instruction.OfElement backLink(Ctx ctx, Page page, long id, int aux) {      testableField("back-link", page.name() + ":" + id + ":" + aux);      Query query = page.query(id, aux);      return backLink(ctx.href(query));    }    /**     * Renders the "Go Back" link.     */    final Html.Instruction.OfElement backLink(String href) {      return a(          css("""          border-radius:9999px          padding:6rx          margin:6rx_0_0_-6rx          position:absolute          active:background-color:btn-ghost-active          hover:background-color:btn-ghost-hover          """),          dataOnClick(this::navigate),          href(href),          rel("nofollow"),          icon(              Kino.Icon.ARROW_LEFT,              css("""              height:20rx              width:20rx              """)          )      );    }    final Html.Instruction.OfAttribute formAction(Ctx ctx, Page page, long id) {      testableField("action", page.name() + ":" + id);      return action(ctx.action(page, id));    }    final Html.Instruction.OfAttribute formAction(Ctx ctx, Page page, long id, int aux) {      testableField("action", page.name() + ":" + id + ":" + aux);      return action(ctx.action(page, id, aux));    }    /**     * Renders a Lucide SVG icon.     */    final Html.Instruction icon(Kino.Icon icon, Html.Instruction... more) {      return svg(          xmlns("http://www.w3.org/2000/svg"), width("24"), height("24"), viewBox("0 0 24 24"),          fill("none"), stroke("currentColor"), strokeWidth("2"), strokeLinecap("round"), strokeLinejoin("round"),          flatten(more),          raw(icon.contents)      );    }    /*     * Typically we would use Script::navigate for "boosted" links.     * But a regular Script::navigate performs a scrollTo(0,0) after     * the request is completed. We don't want that as this demo is     * embedded in another page. In other words, we want the scroll     * position to remain the same after, e.g., we click on a movie.     */    final void navigate(Script script) {      var el = script.element();      script.request(req -> {        req.method(Script.GET);        req.url(el.attr(Html.AttributeName.HREF));        req.onSuccess(() -> {          var shell = script.elementById(Shell.APP);          shell.scroll(0, 0);        });      });    }  }  //  // Configuration related classes  //  /**   * Application-level context.   */  record Ctx(      Clock clock,      KinoCodec codec,      Note.Sink noteSink,      Reservation reservation,      Transactional transactional  ) {    static Ctx of(LandingDemoConfig config) {      final Clock clock;      clock = config.clock;      final byte[] codecKey;      codecKey = config.codecKey();      final KinoCodec codec;      codec = new KinoCodec(clock, codecKey);      final Note.Sink noteSink;      noteSink = config.noteSink;      final Reservation reservation;      reservation = new Reservation(clock, config.reservationEpoch, config.reservationRandom);      final Transactional transactional;      transactional = new Transactional(config.stage, config.database);      return new Ctx(clock, codec, noteSink, reservation, transactional);    }    final String action(Page page, long id) {      return action(page, id, 0);    }    final String action(Page page, long id, int aux) {      Query query;      query = page.query(id, aux);      String demo;      demo = codec.encode(query);      return "/demo/landing?demo=" + demo;    }    final String href(Page page) {      return href(page, 0L);    }    final String href(Page page, long id) {      Query query;      query = page.query(id);      return href(query);    }    final String href(Query query) {      String demo;      demo = codec.encode(query);      return "/index.html?demo=" + demo;    }    final long nextReservation() {      return reservation.next();    }    final <T1> void send(Note.Ref1<T1> note, T1 v1) {      noteSink.send(note, v1);    }    final LocalDateTime today() {      return LocalDateTime.now(clock);    }    final Html.Component transactional(Http.Exchange http, GET action) {      return transactional.get(http, action);    }    final PostResult transactional(Http.Exchange http, POST action) {      return transactional.post(http, action);    }    private Query decode(Http.Exchange http) {      // We cannot rely on the path to render the different pages      // of the application because this demo will be embedded in another page.      // So, we use an URL query parameter.      final String demo;      demo = http.queryParam("demo");      // the query parameter value is encoded/obfuscated.      // we use the codec to decode it.      final Query query;      query = codec.decode(demo);      http.set(Query.class, query);      return query;    }  }  //  // SQL related classes  //  private static final class Transactional {    private final Kino.Stage stage;    private final Sql.Database db;    Transactional(Kino.Stage stage, Sql.Database db) {      this.stage = stage;      this.db = db;    }    public final Html.Component get(Http.Exchange http, Kino.GET action) {      return execute(http, action::get);    }    public final Kino.PostResult post(Http.Exchange http, Kino.POST action) {      return execute(http, action::post);    }    private <T> T execute(Http.Exchange http, Action<T> action) {      return switch (stage) {        case DEFAULT -> {          final Sql.Transaction trx;          trx = db.beginTransaction(Sql.READ_COMMITED);          try {            trx.sql("set schema CINEMA");            trx.update();            http.set(Sql.Transaction.class, trx);            final T result;            result = action.execute(http);            trx.commit();            yield result;          } catch (Throwable e) {            throw trx.rollbackAndWrap(e);          } finally {            trx.close();          }        }        // this is a no-op during testing.        case TESTING -> action.execute(http);      };    }  }  //  // Embedded related classes  //  // Most Objectos Way applications will not require the classes in this section.  // They are required because this demo will be embedded in another application.  //  /**   * Represents an HTTP action in the demo application.   */  @FunctionalInterface  private interface Action<T> {    T execute(Http.Exchange http);  }  /**   * Handles a GET request. Similar to a {@code Http.Handler} instance, but for   * an embedded application.   */  interface GET {    Html.Component get(Http.Exchange http);  }  /**   * Handles a POST request. Similar to a {@code Http.Handler} instance, but for   * an embedded application.   */  interface POST {    PostResult post(Http.Exchange http);  }  /**   * The pages of this application.   *   * <p>   * As a reminder, as this application will be embedded in another one, it does   * not have actual pages.   */  enum Page {    NOW_SHOWING,    MOVIE,    SEATS,    CONFIRM,    TICKET,    BAD_REQUEST;    final Query query() {      return new Query(this, 0L, 0);    }    final Query query(long id) {      return new Query(this, id, 0);    }    final Query query(long id, int aux) {      return new Query(this, id, aux);    }  }  /**   * Represents the demo query parameter.   */  record Query(Page page, long id, int aux) {    final int idAsInt() {      return (int) id;    }  }}
/* * Copyright (C) 2024-2025 Objectos Software LTDA. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package demo.landing.app;import java.util.ArrayList;import java.util.List;import java.util.Objects;import java.util.function.Consumer;import objectos.way.Css;import objectos.way.Html;import objectos.way.Syntax;/** * The demo UI shell responsible for displaying the application on the * top/right and the source code on the bottom/left. */@Css.Source // Indicates to the CSS Generator that it should scan this class for CSS utilities.final class Shell extends Kino.View {  static final class Builder {    String appFrame;    Html.Component app;    String sourceFrame;    private final List<SourceModel> sources = new ArrayList<>();    private Builder() {}    final void sources(SourceModel... values) {      for (SourceModel value : values) {        sources.add(value);      }    }    private Shell build() {      Objects.requireNonNull(appFrame, "appFrame == null");      Objects.requireNonNull(app, "app == null");      Objects.requireNonNull(sourceFrame, "sourceFrame == null");      sources.add(Source.Kino);      sources.add(Source.Shell);      sources.add(Source.SourceModel_);      return new Shell(this);    }  }  static final Html.Id APP = Html.Id.of("demo-app");  private final Builder builder;  private Shell(Builder builder) {    this.builder = builder;  }  public static Shell create(Consumer<Builder> config) {    final Builder builder;    builder = new Builder();    config.accept(builder);    return builder.build();  }  private final Html.Id sourceFrame = Html.Id.of("source-frame");  private final Html.AttributeName dataButton = Html.AttributeName.of("data-button");  private final Html.AttributeName dataPanel = Html.AttributeName.of("data-panel");  private final Html.AttributeName dataSelected = Html.AttributeName.of("data-selected");  @Override  protected final void render() {    // the demo container    div(        css("""        display:grid        grid-template:'a'_448rx_'b'_auto_'c'_448rx_/_1fr        lg:grid-template:'c_a'_512rx_'b_b'_auto_/_1fr_1fr        xl:grid-template:'b_c_a'_512rx_/_200rx_1fr_1fr        """),        renderFragment(this::renderApp),        renderFragment(this::renderSourceMenu),        renderFragment(this::renderSourceCode)    );  }  private void renderApp() {    // the app container    div(        APP,        css("""        border:1px_solid_border        overflow:auto        grid-area:a        position:relative        lg:border-bottom-width:1px        lg:border-left-width:0px        """),        div(            css("""            align-items:center            background-color:layer            color:gray-500            display:flex            height:64rx            justify-content:space-between            padding:0_16rx            position:sticky            top:0px            z-index:8000            """),            a(                css("""                align-items:center                display:flex                gap:6rx                height:100%                """),                href("/index.html"),                dataOnClick(this::navigate),                objectosLogo(),                span(                    css("""                    font-size:24rx                    font-weight:300                    line-height:1                    transform:translateY(-1px)                    """),                    text("kino")                )            ),            a(                css("""                align-items:center                border-radius:6rx                display:flex                padding:8rx                active:background-color:btn-ghost-active                hover:background-color:btn-ghost-hover                """),                href("https://github.com/objectos/demo.landing"),                gitHubLogo()            )        ),        div(            css("""            padding:0_16rx_16rx            h2:font-size:36rx            h2:font-weight:200            h2:line-height:1            h2:padding:48rx_0_8rx            """),            dataFrame("demo-app", builder.appFrame),            renderComponent(builder.app)        )    );  }  private Html.Instruction objectosLogo() {    return svg(        css("""        width:auto        height:24rx        fill:logo        transition:fill_300ms_ease        """),        xmlns("http://www.w3.org/2000/svg"), width("200"), height("49.28"), viewBox("0 0 200 49.28"),        path(            d("m189.6 38.21q-2.53 0-5.3-0.932-2.76-0.903-4.6-3.087-0.38-0.495-0.35-1.02 0.12-0.582 0.64-0.99 0.5-0.32 1.02-0.233 0.58 0.09 0.93 0.524 1.4 1.66 3.38 2.33 2.04 0.641 4.43 0.641 4.08 0 5.77-1.456 1.71-1.456 1.71-3.408 0-1.893-1.86-3.087-1.78-1.281-5.56-1.806-4.87-0.669-7.2-2.621-2.33-1.951-2.33-4.514 0-2.417 1.23-4.077 1.19-1.689 3.29-2.534 2.12-0.874 4.86-0.874 3.29 0 5.56 1.223 2.31 1.165 3.7 3.146 0.38 0.495 0.24 1.077-0.1 0.525-0.73 0.874-0.47 0.233-1.02 0.146-0.53-0.09-0.9-0.583-1.23-1.543-2.97-2.33-1.69-0.815-3.99-0.815-3.06 0-4.75 1.31-1.69 1.311-1.69 3.146 0 1.252 0.67 2.242 0.73 0.903 2.33 1.602 1.6 0.612 4.28 1.02 3.64 0.466 5.71 1.63 2.15 1.165 3.03 2.738 0.9 1.485 0.9 3.233 0 2.301-1.46 3.99-1.45 1.689-3.81 2.621-2.39 0.874-5.16 0.874zm-27.97 0q-3.9 0-6.99-1.748-3.05-1.805-4.85-4.863-1.75-3.088-1.75-6.932 0-3.873 1.75-6.931 1.8-3.117 4.85-4.864 3.09-1.806 6.99-1.806 3.89 0 6.95 1.806 3.05 1.747 4.8 4.864 1.81 3.058 1.81 6.931 0 3.844-1.81 6.932-1.75 3.058-4.8 4.863-3.06 1.748-6.95 1.748zm0-2.709q3.07 0 5.43-1.427 2.45-1.456 3.79-3.873 1.42-2.476 1.42-5.592 0-3.058-1.42-5.475-1.34-2.476-3.79-3.874-2.36-1.456-5.43-1.456-3.03 0-5.45 1.456-2.41 1.398-3.83 3.874-1.4 2.417-1.4 5.533 0 3.058 1.4 5.534 1.42 2.417 3.83 3.873 2.42 1.427 5.45 1.427zm-19.01 2.418q-2.65-0.06-4.75-1.224-2.09-1.194-3.26-3.291-1.16-2.126-1.16-4.805v-24.17q0-0.67 0.4-1.078 0.44-0.436 1.05-0.436 0.7 0 1.08 0.436 0.44 0.408 0.44 1.078v24.17q0 2.825 1.74 4.601 1.75 1.748 4.52 1.748h1.08q0.67 0 1.05 0.437 0.43 0.407 0.43 1.077 0 0.641-0.43 1.078-0.38 0.379-1.05 0.379zm-12.96-22.95q-0.58 0-0.96-0.35-0.35-0.378-0.35-0.961 0-0.582 0.35-0.932 0.38-0.378 0.96-0.378h13.16q0.59 0 0.94 0.378 0.38 0.35 0.38 0.932 0 0.583-0.38 0.961-0.35 0.35-0.94 0.35zm-13.09 23.24q-3.78 0-6.79-1.806-2.96-1.776-4.71-4.834-1.7-3.059-1.7-6.903 0-3.873 1.6-6.931t4.42-4.806q2.81-1.806 6.45-1.806 3.1 0 5.7 1.224 2.56 1.165 4.45 3.582 0.38 0.495 0.29 1.019-0.1 0.525-0.64 0.874-0.44 0.349-0.96 0.291-0.52-0.09-0.93-0.582-3.09-3.699-7.91-3.699-2.86 0-5.04 1.427-2.14 1.398-3.35 3.815-1.17 2.447-1.17 5.592 0 3.058 1.31 5.534 1.31 2.417 3.59 3.873 2.33 1.427 5.39 1.427 1.99 0 3.74-0.582 1.81-0.583 3.12-1.806 0.44-0.379 0.96-0.437 0.52-0.06 0.93 0.35 0.47 0.437 0.47 1.019 0.1 0.524-0.38 0.903-3.55 3.262-8.84 3.262zm-27.69-0.06q-3.84 0-6.85-1.69-2.96-1.747-4.66-4.805t-1.7-6.99q0-3.99 1.61-6.99 1.6-3.058 4.41-4.805 2.82-1.748 6.46-1.748 3.59 0 6.36 1.69 2.76 1.66 4.31 4.63 1.56 2.913 1.56 6.728 0 0.641-0.39 1.019-0.39 0.35-1.02 0.35h-21.35v-2.534h22.13l-2.14 1.602q0.1-3.146-1.07-5.563-1.16-2.446-3.35-3.786-2.13-1.427-5.04-1.427-2.77 0-4.95 1.427-2.14 1.34-3.4 3.786-1.21 2.417-1.21 5.621 0 3.146 1.31 5.592 1.31 2.417 3.64 3.815 2.33 1.369 5.34 1.369 1.89 0 3.78-0.641 1.94-0.67 3.06-1.747 0.39-0.379 0.92-0.379 0.58-0.06 0.97 0.291 0.54 0.437 0.54 0.962 0 0.553-0.44 0.99-1.55 1.398-4.08 2.33-2.47 0.903-4.75 0.903zm-30.93 11.13q-0.64 0-1.07-0.44-0.44-0.38-0.44-1.02 0-0.67 0.44-1.1 0.43-0.41 1.07-0.41 2.47 0 4.31-1.05 1.9-1.08 2.97-2.97 1.06-1.89 1.06-4.313v-25.16q0-0.67 0.39-1.049 0.44-0.408 1.07-0.408 0.68 0 1.07 0.408 0.43 0.379 0.43 1.049v25.16q0 3.291-1.45 5.821-1.46 2.57-4.03 4.02-2.52 1.46-5.82 1.46zm9.75-43.48q-0.92 0-1.6-0.641-0.63-0.67-0.63-1.66 0-1.107 0.68-1.631 0.73-0.582 1.6-0.582 0.82 0 1.5 0.582 0.73 0.524 0.73 1.631 0 0.99-0.68 1.66-0.63 0.641-1.6 0.641zm-21.55 32.42q-3.79 0-6.84-1.748-3.06-1.747-4.86-4.747-1.75-3.029-1.84-6.815v-23.44q0-0.67 0.39-1.048 0.43-0.408 1.06-0.408 0.68 0 1.07 0.408 0.39 0.378 0.39 1.048v15.14q1.55-2.505 4.32-4.019 2.82-1.515 6.31-1.515 3.88 0 6.94 1.806 3.11 1.747 4.85 4.805 1.8 3.058 1.8 6.932 0 3.902-1.8 6.99-1.74 3.058-4.85 4.863-3.06 1.748-6.94 1.748zm0-2.709q3.06 0 5.44-1.427 2.42-1.456 3.83-3.873 1.41-2.476 1.41-5.592 0-3.087-1.41-5.534-1.41-2.417-3.83-3.815-2.38-1.456-5.44-1.456-3.01 0-5.44 1.456-2.42 1.398-3.83 3.815-1.36 2.447-1.36 5.534 0 3.116 1.36 5.592 1.41 2.417 3.83 3.873 2.43 1.427 5.44 1.427zm-32.53 2.709q-3.88 0-6.99-1.748-3.06-1.805-4.85-4.863-1.75-3.088-1.75-6.932 0-3.873 1.75-6.931 1.79-3.117 4.85-4.864 3.11-1.806 6.99-1.806t6.94 1.806q3.06 1.747 4.8 4.864 1.8 3.058 1.8 6.931 0 3.844-1.8 6.932-1.74 3.058-4.8 4.863-3.06 1.748-6.94 1.748zm0-2.709q3.06 0 5.44-1.427 2.42-1.456 3.78-3.873 1.41-2.476 1.41-5.592 0-3.058-1.41-5.475-1.36-2.476-3.78-3.874-2.38-1.456-5.44-1.456-3.01 0-5.44 1.456-2.42 1.398-3.83 3.874-1.41 2.417-1.41 5.533 0 3.058 1.41 5.534 1.41 2.417 3.83 3.873 2.43 1.427 5.44 1.427z"),            strokeWidth(".9101")        )    );  }  private Html.Instruction gitHubLogo() {    return svg(        css("""        width:auto        height:24rx        fill:logo        """),        xmlns("http://www.w3.org/2000/svg"), width("98"), height("96"), viewBox("0 0 98 96"),        path(            fillRule("evenodd"), clipRule("evenodd"), d(                "M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z")        )    );  }  private void renderSourceMenu() {    final List<SourceModel> items;    items = builder.sources;    final SourceModel first;    first = items.getFirst();    // the source file selector    div(        sourceFrame,        css("""        border-left:1px_solid_border        border-right:1px_solid_border        display:flex        font-size:14rx        gap:4rx_8rx        grid-area:b        padding:16rx        overflow-x:auto        lg:border-bottom:1px_solid_border        xl:border-top:1px_solid_border        xl:border-right-width:0px        xl:flex-direction:column        """),        dataFrame("demo-source-menu", builder.sourceFrame),        // stores the current selected button in the data-button attribute        attr(dataButton, first.button().value()),        // stores the current selected panel in the data-panel attribute        attr(dataPanel, first.panel().value()),        renderFragment(this::renderSourceMenuItems)    );  }  private void renderSourceMenuItems() {    final List<SourceModel> items;    items = builder.sources;    for (int idx = 0, size = items.size(); idx < size; idx++) {      final SourceModel item;      item = items.get(idx);      button(          item.button(),          css("""          border-radius:6rx          cursor:pointer          padding:4rx_8rx          [data-selected=true]:background-color:btn-ghost-active          active:background-color:btn-ghost-active          hover:background-color:btn-ghost-hover          """),          attr(dataSelected, Boolean.toString(idx == 0)),          dataOnClick(script -> {            // 'deselects' current            var frame = script.elementById(sourceFrame);            var selectedButton = script.elementById(frame.attr(dataButton));            selectedButton.attr(dataSelected, "false");            var selectedPanel = script.elementById(frame.attr(dataPanel));            selectedPanel.attr(dataSelected, "false");            // 'selects' self            var selfButton = script.elementById(item.button());            selfButton.attr(dataSelected, "true");            var selfPanel = script.elementById(item.panel());            selfPanel.attr(dataSelected, "true");            // stores selected            frame.attr(dataButton, item.button().value());            frame.attr(dataPanel, item.panel().value());          }),          text(item.name())      );    }  }  private void renderSourceCode() {    // the Java source code display    div(        css("""        border:1px_solid_border        display:flex        flex-direction:column        grid-area:c        """),        css("""        flex:1        min-height:0        overflow:auto        """),        dataFrame("demo-source-code", builder.sourceFrame),        renderFragment(this::renderSourceCodeItems)    );  }  private void renderSourceCodeItems() {    final List<SourceModel> items;    items = builder.sources;    for (int idx = 0, size = items.size(); idx < size; idx++) {      final SourceModel item;      item = items.get(idx);      final String source;      source = item.value();      pre(          item.panel(),          css("""          display:none          font-family:mono          font-size:13rx          line-height:18.6rx          padding:16rx          [data-selected=true]:display:flex          span:[data-line]:display:block          span:[data-line]:min-height:1lh          span:[data-line]:nth-child(-n+15):display:none          span:[data-high=annotation]:color:high-meta          span:[data-high=comment]:color:high-comment          span:[data-high=comment]:font-style:italic          span:[data-high=keyword]:color:high-keyword          span:[data-high=string]:color:high-string          """),          attr(dataSelected, Boolean.toString(idx == 0)),          code(              css("""              flex-grow:1              """),              renderComponent(                  Syntax.highlight(Syntax.JAVA, source)              )          )      );    }  }}
/* * Copyright (C) 2024-2025 Objectos Software LTDA. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package demo.landing.app;import objectos.way.Html;record SourceModel(String name, String value, Html.Id button, Html.Id panel) {  private static int INDEX = 0;  public static SourceModel create(String name, String value) {    final int index;    index = INDEX++;    return new SourceModel(        name,        value,        Html.Id.of("src-btn-" + index),        Html.Id.of("src-panel-" + index)    );  }}